Skip to content

mcpx(cache): Scan counts a file entry's lease file as an entry — Prune can delete the live lease and strip the pin from an in-use entry #3112

Closed

Description

@dezren39

Problem

pkgs/mcpx/internal/cache's Scan treats a file entry's lease file as a cache entry in its own right. A lease for a directory entry lives inside the entry (invisible to a scan of the parent), but a lease for a single-file entry lives beside it, named <entry>.lease-<random> (leaseLocation, cache.go:222-231). Scan (cache.go:265-310) only skips names starting with the bare lease prefix .lease- (cache.go:276) or .tmp- — a beside-file lease name starts with the entry's name, so it passes the skip, passes a nil match, and is returned as an Entry with Leased: false.

Consequences, all in the mechanism whose core promise is "eviction never removes a leased entry … the lease is what makes that safe" (cache.go:14-17):

Evidence (verified this run, main 69b23978)

Verbatim-copy probe on the laptop (cache.go sha256 2782f988… identical in both clones; probe dir ~/git/fleet/probe-2026-10-31-laneB-cachelease/ on the laptop):

  • Scan(dir, "kind", 0, nil) over a directory holding one leased file entry returned 2 entries: key="entry.bin" size=7 leased=true and key="entry.bin.lease-3358895815" size=0 leased=false.
  • With the lease file aged oldest and the entry newest, Prune(dir, Limits{MaxEntries: 1}, "", nil) removed exactly one entry: entry.bin.lease-566081972 — the live lease file itself — leaving the in-use entry unpinned.

The nil-match form is not hypothetical: Cache.List (cache.go:254) and the runner's pruneWorkDirs (internal/runner/runner.go:839-846) both scan with no pattern, and the package documents entries as "either a directory or a single file" (cache.go:8-9) with Acquire explicitly supporting file entries (cache.go:195-198). The runner's patterned pruners escape only because their suffix patterns (.ts, .mcpx-entry.ts) happen not to match a lease suffix.

Why it matters

The lease is the cache mechanism's only protection against concurrent eviction (#1908, #2566). A scan that cannot tell a lease from an entry both corrupts eviction accounting and can destroy the pin itself, re-opening the exact failure #2566 finding 3 closed (an in-use entry deleted from under a running exec) through the lease file rather than the entry.

Acceptance criteria

  • Scan never returns a lease file as an entry: beside-file leases (<name>.lease-*) are skipped (or otherwise recognised) regardless of the match pattern, including the nil-pattern form.
  • Prune/Evict/removeAll can never delete a lease file; a victim list containing one is impossible, not just rechecked.
  • Regression tests in internal/cache: a leased file entry scans as exactly one entry with Leased: true, and a prune sized to force a victim leaves the lease file and the entry in place.
  • Existing internal/cache and runner tests still pass (laptop or CI).

Dedup

Distinct from closed #2566 (path traversal in Remove, Put self-eviction, scan→remove lease TOCTOU recheck): this is lease files being enumerated as entries by Scan, which neither the traversal validation nor the removeAll recheck addresses. Searched open + closed issues for cache/Scan/lease/Prune terms; no existing issue covers it. Parent mechanism: #1908.

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/triageQueue packet kind: verify, reproduce, and route this item
    source/creatorFiled by the new-issue-creator job
    priority/lowNice to have; do when convenient
    queue/queuedHas a queued task in the Supabase agent queue
    app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)
    effort/sSmall: expected under 2 hours of work
    on Oct 31, 2026
  2. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator job (new-issue-creator-code-b)
    • Session / ID: new-issue-creator-code-b cron run 2026-10-31T15:20Z (America/Chicago)
    • Found via: filed this run
    • Task: Filed this issue: mcpx cache Scan enumerates a file entry's beside-lease file as an entry, and Prune can delete the live lease, stripping the pin from an in-use entry (verified with a laptop verbatim-copy probe of internal/cache/cache.go at main 69b2397)
    • Expectation: effort/s — triage confirms the phantom-entry scan and routes the fix (skip beside-file leases in Scan; regression tests)
    • Links: queue packet packet-3112-triage.json (repo-health-code-pilot)
  3. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  4. 81 remaining items

  5. added
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 1, 2026
  6. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

  7. added
    queue/claimedQueue worker lifecycle state
    and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Nov 1, 2026
  8. added a commit that references this issue on Nov 1, 2026
  9. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

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

Metadata

Metadata

Assignees

Labels

app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)effort/sSmall: expected under 2 hours of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/lowNice to have; do when convenientsource/creatorFiled by the new-issue-creator jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions