Skip to content

pm install <local-dir> copies the destination into itself when the tracker is inside the package, nesting one full copy per install until the disk fills (reports ok: true throughout) #827

Closed

Description

@unbraind

pm --version 2026.7.29. This one filled a 914 GB disk while I was developing a package, so filing with a minimal repro.

What happens

For a package whose own tracker lives inside the package directory — the standard layout for every package in our fleet, since pm init puts it at ./.agents/pm — pm install . copies the package into <pmRoot>/extensions/<name>/ without excluding the destination from the source walk. The destination is inside the source, so each install nests the previous install inside the new one, and the backup taken on re-install nests the previous backup too.

Minimal repro

mkdir probe && cd probe && git init -q
# package.json: { "name": "pm-probe", "pm": { "aliases": ["probe"], "extensions": ["."] } }
# manifest.json: { "name": "pm-probe", "entry": "./dist/index.js", "capabilities": ["commands"], ... }
# dist/index.js: registers one trivial command
pm init probe --pm-path "$PWD/.agents/pm" --yes

pm install . --pm-path "$PWD/.agents/pm"     # ok: true
find .agents/pm/extensions -type d -name extensions
#   .agents/pm/extensions
#   .agents/pm/extensions/pm-probe/.agents/pm/extensions          <-- copied itself in

pm install . --pm-path "$PWD/.agents/pm"     # ok: true
#   + .agents/pm/extensions/pm-probe/.agents/pm/extensions/.pm-extension-install-backup-iLC5ds/destination/.agents/pm/extensions

pm install . --pm-path "$PWD/.agents/pm"     # ok: true
#   + .../.pm-extension-install-backup-yYAkgj/destination/.agents/pm/extensions/.pm-extension-install-backup-iLC5ds/destination/.agents/pm/extensions

Every install adds a level that contains all previous levels, so the tree does not grow linearly — install N holds a copy of install N−1 in its entirety.

Why it is destructive rather than merely untidy

The copy includes node_modules. For a real package that is 150–300 MB per level, so the third or fourth pm install . during ordinary development writes tens of gigabytes. Mine died as:

ENOSPC: no space left on device, copyfile
'/tmp/pm-extension-copy-lsGSf2/extension/.agents/pm/extensions/.pm-extension-install-backup-SOQ4zL/destination/node_modules/@opentelemetry/resources/build/esnext/detectors/platform/node/machine-id/getMachineId.d.ts'
-> '.../fleet/pm-vcs/.agents/pm/extensions/pm-vcs/.agents/pm/extensions/.pm-extension-install-backup-SOQ4zL/destination/node_modules/...'

Note both sides of that path: the source already contained a nested backup, and it was being copied into a deeper one.

Two things make this hard to notice before it hurts:

  • Every install reports ok: true. Nothing warns that the destination is inside the source.
  • .agents/pm/extensions/ is gitignored (correctly — pm init sets that up), so the growth is invisible to git status and to review. It shows up only as a full disk.

Re-running pm install . is completely normal during package development: build, install, exercise the command, fix, repeat. That is the loop that fills the disk.

Suggested fix

  1. Prune the destination from the source walk. When the resolved destination (<pmRoot>/extensions/<name>) is contained in the source directory, skip it — the standard copy-into-self guard. Same for the backup directory.
  2. Prune node_modules and the tracker root while copying a local package directory. Neither belongs in an installed extension copy; the entry point plus declared resources is what activation needs. This alone would have kept the damage to kilobytes.
  3. Refuse, or warn loudly, when destination ⊂ source rather than reporting ok: true.

(1) is the correctness fix; (2) is what makes the operation cheap enough that the loop above stays safe.

Activity

  1. coderabbitai commented on Jul 30, 2026

    @coderabbitai
    🔗 Related PRs

    #73 - Fix nightly macOS realpath + Windows .cmd EINVAL and dedup extension-policy override builder [closed]
    #302 - Optimize npm package SDK installs [merged]
    #428 - Harden SDK extension runtime packaging [merged]
    #475 - Improve package install and annotation agent UX [merged]
    #491 - Harden init target safety and package install verification [merged]


    📝 Issue Planner

    Check the box below or use the @coderabbitai plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.

    • Create Plan

    🧪 Issue enrichment is currently in open beta.

    You can configure auto-planning by selecting labels in the issue_enrichment configuration.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  2. unbraind commented on Jul 30, 2026

    @unbraind
    OwnerAuthor

    Tracked canonically as pm-0682l4 after an all-status duplicate search. The PM item records scope, acceptance criteria, lineage, and this GitHub intake; it remains open and unclaimed until actively implemented.

  3. unbraind commented on Aug 21, 2026

    @unbraind
    OwnerAuthor

    The containment recursion is fixed in the currently installed pm 2026.8.20, but the local-install copy still includes repository and development-only payloads.

    Current evidence from a package whose tracker is inside its repository:

    pm install . --project
    # reports overwritten: true and installed_in_place: false
    # destination size: 252M
    # top-level copy includes .git, coverage, dist-test, node_modules, source, tests, and workflows
    # .agents/pm/extensions/pm-ops/.git is a directory
    

    This leaves an ignored managed-extension directory as a second functioning Git repository and copies hundreds of MB during the normal build/install/dogfood loop. It also conflicts with the original issue's still-relevant acceptance suggestion to prune .git, node_modules, the tracker, and development artifacts. The source-contained destination itself was correctly pruned, so growth is no longer recursive.

    Could the canonical open PM item retain the unresolved pruning requirement and add a regression asserting that a local install never contains .git or unrelated development outputs? Runtime dependency handling may need a package/install contract rather than copying the complete working tree.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions