Skip to content

fix(pool): scan processes once per status listing and narrow lock scope - #195

Draft
PabloSzx wants to merge 8 commits into
kunchenguid:mainfrom
PabloSzx:pablosaez/status-scan-efficiency
Draft

PabloSzx wants to merge 8 commits into
kunchenguid:mainfrom
PabloSzx:pablosaez/status-scan-efficiency

Conversation

@PabloSzx

@PabloSzx PabloSzx commented Oct 21, 2026 •

Copy link
Copy Markdown

What Changed

  • treehouse status (pool.List) now runs recovery, healing, and the state write under the pool lock, then releases the lock. It scans processes once for the whole pool using the new process.FindProcessesInWorktrees, which reads each process cwd once and still matches symlinked and nested roots. If that scan fails, status prints one pool-wide warning and marks the affected slots unverified.
  • Each slot then gets a short pool-lock section (withPoolLock, which skips the recovery pass). Inside it, status re-reads the slot's state entry and checks the marker, branch, and dirty state. Slots that were removed, replaced, or marked Destroying after the snapshot are left out of the listing. A slot that only the batch scan would mark in-use is scanned again under the lock, so a short-lived Git run from another treehouse command doesn't show up as an occupant.
  • Git's IsDirty now runs status --no-optional-locks --untracked-files=normal --no-renames, so it doesn't write the index or take index.lock during observation. README and docs/design.md describe the new locking behavior. New tests cover status classification, the Unix concurrency cases, batched process detection, and the dirty check.

Risk Assessment

✅ Low: I traced the current code for the races raised in earlier rounds. The round-2 fix (re-read state under the per-slot lock) and the round-3 fix (re-scan processes under that lock when they alone would mark a slot in-use) both close their sequences, the batched process matching keeps the old single-path semantics, and the dirty-check flags only lower the risk of locking the index, so the only remaining finding is documentation drift.

Testing

I tested both builds through the CLI in a disposable pool with its own HOME and TREEHOUSE_ROOT, and tore it down afterwards. On a fixture covering leased, dirty (nested untracked file, staged rename, tracked edit), in-use through a symlinked path, you're-here and damaged slots, both builds produced byte-identical status --json. HEAD's status was about 37% faster: roughly 815 ms against 1290 ms. HEAD left a stale slot index untouched, while base rewrote it on every run. To show that, I had to unset GIT_OPTIONAL_LOCKS, because the harness exports it as 0 and that hides the difference. With four agents polling status, HEAD's get/return cycles never failed and never skipped a slot. No idle slot was reported in-use and there were no index.lock errors. get latency fell from 5–23 s to about 0.9 s. When I destroyed a slot during an in-flight status, that run omitted the slot instead of reporting it damaged. A slot leased during an in-flight status was reported leased with its lease id when the lease landed early; in the latest-timed run it was reported available. Two timing-dependent paths could not be driven reliably through the CLI: a process that exits between the batched scan and the locked re-scan, and a failed re-scan that should report unverified. I covered those with the targeted unit tests, which pass along with the other listing, process-matching, dirty-check and cmd status tests and the Windows cross-build. Because those two paths were not driven live, that scenario is recorded as untested. Nothing failed. This is a CLI-only change, so the evidence is CLI transcripts, not screenshots.

  • Live validation: ✅ go - 8 of 9 scenarios driven live against the product
Scenario Result Live Evidence
Running treehouse status on a pool with leased, dirty (nested untracked file with showUntrackedFiles=no, staged rename only, tracked edit), in-use (process reached via the /tmp symlink), you're-here… ✅ pass live status-parity.txt: table output matches and status --json from head and base is IDENTICAL, with GIT_OPTIONAL_LOCKS both set and unset
treehouse status on a 15-slot pool returns faster than the base build ✅ pass live status-timing.txt: BASE about 1290 ms against HEAD about 815 ms mean over 3 interleaved rounds of 8 runs
Adversarial: treehouse status on a slot whose index is stale (core.untrackedCache=true, optional locks enabled) does not rewrite the index or take index.lock, and still reports the slot available ✅ pass live index-not-written.txt: BASE REWRITTEN on both runs, HEAD UNCHANGED on both runs; slot 4 still listed as available
Adversarial: while several agents poll status, another agent's get --lease/return cycles always succeed, reuse the available slot without growing the pool, and see no index.lock errors ✅ pass live concurrency.txt run 1: 0 lifecycle failures, slot 4 handed out 20/20 times, pool size stayed 15, 0 index.lock errors
Adversarial: concurrent status observers (and concurrent get/return) never make an idle slot read in-use from another command's short-lived git child ✅ pass live concurrency.txt run 1: in 29 concurrent status runs on HEAD, no idle slot was reported in-use
Lifecycle commands are no longer blocked for the whole listing: get --lease latency under continuous status polling drops ✅ pass live concurrency.txt run 2: get latency 5–23 s on BASE against about 0.9 s on HEAD; status median latency 6.0 s against 5.2 s
Adversarial: a slot destroyed while a status run is in flight is omitted from that listing, not reported damaged ✅ pass live race-destroy-lease.txt: slots 15 and 14 destroyed mid-listing were OMITTED, slot 13 (destroyed after it was read) showed available, and only the intentionally broken slot 5 was damaged
A slot leased while a status run is in flight is reported from fresh state (leased, with its lease id) ✅ pass live race-destroy-lease.txt: in 5 of 6 staggered runs the concurrent status reported slot 12 as leased with the new lease_id; in the latest-timed run it was reported available
A process seen in a slot by the batched scan but gone by the slot's locked section is reported available, a persisting one stays in-use, and a failed locked re-scan reports unverified ⏸️ untested no The prior payload did not establish a live result for this scenario. Only unit tests covered it (TestListDropsProcessGoneBeforeSlotIsLocked, TestListKeepsProcessStillInSlotWhenLocked, TestListReportsU…
Evidence: Status classification parity, base vs head (table + JSON diff)
# Fixture: 1 leased; 2 untracked file at src/new/deep/x.txt (repo has status.showUntrackedFiles=no); 3 'sleep' running in src/ (pool path via /tmp symlink); 4 caller cwd; 5 broken .git marker; 6 staged rename only; 7 tracked modification
$ cd /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/4/repo/src && treehouse status   # HEAD build
base  main  (repository default)
1     leased       /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/1/repo  (detached)
2     dirty        /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/2/repo  (detached)
3     in-use       /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/3/repo  (detached)
                   sleep (57790)
4     you're here  /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/4/repo  (detached)
5     damaged      /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/5/repo  (branch unreadable: resolving .git marker in /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/5/repo: reading worktree registration backlink for /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/5/repo: open /nonexistent/gitdir/gitdir: no such file or directory)  (inspect the worktree and repair its VCS marker before reuse. Check removal restrictions before deleting it)
6     dirty        /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/6/repo  (detached)
7     dirty        /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/7/repo  (detached)

$ treehouse status   # BASE build (bf61797), same fixture
base  main  (repository default)
1     leased       /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/1/repo  (detached)
2     dirty        /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/2/repo  (detached)
3     in-use       /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/3/repo  (detached)
                   sleep (57790)
4     you're here  /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/4/repo  (detached)
5     damaged      /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/5/repo  (branch unreadable: resolving .git marker in /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/5/repo: reading worktree registration backlink for /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/5/repo: open /nonexistent/gitdir/gitdir: no such file or directory)  (inspect the worktree and repair its VCS marker before reuse. Check removal restrictions before deleting it)
6     dirty        /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/6/repo  (detached)
7     dirty        /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/7/repo  (detached)

$ diff <(jq . base-status.json) <(jq . head-status.json)
IDENTICAL status --json output (byte count     1725)

$ treehouse status --json | jq -c '.[] | {name,status,flavor,processes,branch_error}'   # HEAD
{"name":"1","status":"leased","flavor":"git","processes":[],"branch_error":null}
{"name":"2","status":"dirty","flavor":"git","processes":[],"branch_error":null}
{"name":"3","status":"in-use","flavor":"git","processes":[{"pid":57790,"name":"sleep"}],"branch_error":null}
{"name":"4","status":"you're here","flavor":"git","processes":[],"branch_error":null}
{"name":"5","status":"damaged","flavor":null,"processes":[],"branch_error":"resolving .git marker in /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/5/repo: reading worktree registration backlink for /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/5/repo: open /nonexistent/gitdir/gitdir: no such file or directory"}
{"name":"6","status":"dirty","flavor":"git","processes":[],"branch_error":null}
{"name":"7","status":"dirty","flavor":"git","processes":[],"branch_error":null}
IDENTICAL with GIT_OPTIONAL_LOCKS unset
Evidence: Stale index left unwritten by head status (base rewrites it)
# GIT_OPTIONAL_LOCKS unset. Slot 4 tracked files given an old mtime before each run (stale stat in index); repo core.untrackedCache=true
# sanity: plain git status in slot 4:
#   git status --porcelain: 89835230693c0c23 -> 93aa52deeb820970
BASE bf61797 treehouse status: slot-4 index sha before=93aa52deeb820970 after=ad4bac107af9f822 -> REWRITTEN (status took index.lock)
HEAD 64a7cb2 treehouse status: slot-4 index sha before=ad4bac107af9f822 after=ad4bac107af9f822 -> UNCHANGED (no index.lock taken)
BASE bf61797 treehouse status (repeat): slot-4 index sha before=ad4bac107af9f822 after=5df994c615165007 -> REWRITTEN (status took index.lock)
HEAD 64a7cb2 treehouse status (repeat): slot-4 index sha before=5df994c615165007 after=5df994c615165007 -> UNCHANGED (no index.lock taken)

$ treehouse status  (HEAD) still classifies slot 4 correctly:
4     available    /tmp/th-test.xB1G/pools/.treehouse/repo-3ef334/4/repo  (detached)
Evidence: status latency, base vs head
# 15-slot pool (1 leased, 3 dirty, 1 in-use, 1 damaged, 9 available), 863 host processes; mean of 8 'treehouse status' runs each, interleaved
round 1: BASE bf61797 1323 ms   HEAD 64a7cb2 801 ms
round 2: BASE bf61797 1273 ms   HEAD 64a7cb2 815 ms
round 3: BASE bf61797 1281 ms   HEAD 64a7cb2 828 ms
Evidence: Concurrent status polling vs get/return: correctness and latency

HEAD 64a7cb2: 10x(get --lease + return) wall=19.5s; get latencies ms:[ 931 919 1113 903 917 844 965 858 1034 1381 ] BASE bf61797: 10x(get --lease + return) wall=219.2s; get latencies ms:[ 22966 7254 6433 11334 6201 5750 7758 5221 4964 5386 ]

# Disposable pool of 15 slots (1 leased, 3 dirty, 1 in-use, 1 damaged, 9 available), isolated HOME/TREEHOUSE_ROOT, GIT_OPTIONAL_LOCKS unset.
# Four background loops run `treehouse status --json` continuously while a foreground agent cycles `get --lease --no-fetch` + `return`.

## Run 1: correctness under contention (20 get/return cycles)
HEAD: 20x get --lease + return: lifecycle failures=0, slots handed out:[ 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 ] | 29 concurrent status runs, nonzero exits=0, idle slots falsely reported in-use=[none], index.lock errors=0, pool size after=15
BASE: 20x get --lease + return: lifecycle failures=0, slots handed out:[ 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 ] | 244 concurrent status runs, nonzero exits=0, idle slots falsely reported in-use=[none], index.lock errors=0, pool size after=15
(get always reused the lowest available slot, never skipped it or grew the pool; no status run reported an idle slot in-use from another observer's git child)

## Run 2: latency under contention (10 get/return cycles)
HEAD 64a7cb2: 10x(get --lease + return) wall=19.5s;  get latencies ms:[ 931 919 1113 903 917 844 965 858 1034 1381 ];       status runs=17,  status latency ms: median=5206 max=6619
BASE bf61797: 10x(get --lease + return) wall=219.2s; get latencies ms:[ 22966 7254 6433 11334 6201 5750 7758 5221 4964 5386 ]; status runs=124, status latency ms: median=6037 max=24357
Evidence: Destroy/lease racing an in-flight status
# Race: start 'treehouse status --json' (HEAD), then after a delay destroy/lease a slot from another process; record what that status run reported for the slot.
destroy slot 15 after 0.15s: destroy rc=0; concurrent status rc=0 reported slot 15 as: OMITTED; damaged entries total=1 (slot 5 is intentionally damaged)
destroy slot 14 after 0.3s: destroy rc=0; concurrent status rc=0 reported slot 14 as: OMITTED; damaged entries total=1 (slot 5 is intentionally damaged)
destroy slot 13 after 0.45s: destroy rc=0; concurrent status rc=0 reported slot 13 as: available; damaged entries total=1 (slot 5 is intentionally damaged)
lease slot 12 after 0.1s: lease rc=0; concurrent status reported slot 12 as: leased lease_id=9da315043b1429972b2caff6352dfed7
lease slot 12 after 0.2s: lease rc=0; concurrent status reported slot 12 as: leased lease_id=b463d694d6729c1c1d82fa6e9f4cd2c0
lease slot 12 after 0.3s: lease rc=0; concurrent status reported slot 12 as: leased lease_id=4e09e3e84ce5cab3fdb8af5498b4466f
lease slot 12 after 0.4s: lease rc=0; concurrent status reported slot 12 as: leased lease_id=94030a70af9310e5fe4a7bcf245cab55
lease slot 12 after 0.5s: lease rc=0; concurrent status reported slot 12 as: leased lease_id=39c93336deece38943f1be73ad1d8e37
lease slot 12 after 0.6s: lease rc=0; concurrent status reported slot 12 as: available lease_id=

Pipeline

Updates from git push no-mistakes

⏭️ **intent** - skipped

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ⚠️ internal/pool/pool.go:1254 - inspectListedWorktrees takes the lock again for each slot and reads a fresh marker there, but it still classifies the slot from the wt captured in the earlier WithStateLock snapshot. That snapshot supplies Destroying, Leased/LeaseID/LeaseHolder, the owner fields used by ownerAlive(wt), and RecoveryError. Lifecycle operations can now run between the snapshot and a slot's locked section, so that section can mix fresh marker and VCS facts with stale membership and reservation facts. Before this change, List held one lock for the whole pass, so this could not happen. Example: treehouse status captures slot X, and while it scans processes or runs Git on earlier slots, treehouse prune --yes reserves X and removes it under the lock (prune.go:495-562), dropping the entry. When X's locked section runs, WorktreeBackendNameChecked fails on the missing path, so status reports X as damaged with marker-inspection guidance even though X is no longer in the pool. enter X would also accept it and try to spawn a shell in a missing directory. The same stale data makes a slot leased by treehouse lease or get --lease during the listing read available, which is exactly the sequence TestListAllowsLeaseDuringProcessInspection drives; that test only checks that the state is not overwritten. Fix at the boundary the change already added: inside the per-slot withPoolLock, ReadState once (without healing), look up the entry by name and path, skip it if it is gone or Destroying, and classify from the fresh entry. This costs one small file read per slot, which is cheap next to git status, and keeps recovery and state writes in the single WithStateLock section.

🔧 Fix applied.
2 warnings still open:

  • ⚠️ internal/pool/pool.go:1254 - inspectListedWorktrees takes the lock again for each slot and reads a fresh marker there, but it still classifies the slot from the wt captured in the earlier WithStateLock snapshot. That snapshot supplies Destroying, Leased/LeaseID/LeaseHolder, the owner fields used by ownerAlive(wt), and RecoveryError. Lifecycle operations can now run between the snapshot and a slot's locked section, so that section can mix fresh marker and VCS facts with stale membership and reservation facts. Before this change, List held one lock for the whole pass, so this could not happen. Example: treehouse status captures slot X, and while it scans processes or runs Git on earlier slots, treehouse prune --yes reserves X and removes it under the lock (prune.go:495-562), dropping the entry. When X's locked section runs, WorktreeBackendNameChecked fails on the missing path, so status reports X as damaged with marker-inspection guidance even though X is no longer in the pool. enter X would also accept it and try to spawn a shell in a missing directory. The same stale data makes a slot leased by treehouse lease or get --lease during the listing read available, which is exactly the sequence TestListAllowsLeaseDuringProcessInspection drives; that test only checks that the state is not overwritten. Fix at the boundary the change already added: inside the per-slot withPoolLock, ReadState once (without healing), look up the entry by name and path, skip it if it is gone or Destroying, and classify from the fresh entry. This costs one small file read per slot, which is cheap next to git status, and keeps recovery and state writes in the single WithStateLock section.
  • ⚠️ internal/pool/pool.go:1238 - status can report an idle slot as in-use because its process scan catches Git processes that another treehouse command runs in that slot. The batched scan (findProcessesInWorktrees, pool.go:1216) now runs outside the pool lock, and List later uses its results as-is under each slot's lock (pool.go:1238 → Processes: procs at 1269 → len(procs) &gt; 0 → StatusInUse at 1292). Other commands still run Git in the slot directory while holding that lock: a second List's per-slot CheckedOutBranch/IsDirty (cmd.Dir = slot), return's reset/detach, and Acquire's reset. Example: agents A and B both run treehouse status, which is common since agents poll it. B holds the lock while its git status runs in available slot X. A's unlocked scan sees B's git child with its cwd in X. When A gets X's lock, B's git has already exited, yet A reports X as in-use with a dead git PID. In the same way, a concurrent treehouse return X makes A report the just-returned slot in-use with the agent PIDs that return killed or its own git PIDs. Before this change, List held one lock for the scan and the classification, so neither sequence could happen. It also changes behavior: return --all (return_cmd.go:263, returnableStatus) then targets the idle slot and can kill the other observer's git. This defect dates from the original commit 4e982c2. The round-2 fix moved the Git reads back under a per-slot lock but left the scan results unrechecked, so it did not close it. Smallest fix, which keeps the scan outside the lock: inside the per-slot withPoolLock (pool.go:1247), before classifying, drop scanned processes that are no longer alive (for example with process.StartedAt(pid)). Any Git child that ran under another lock holder has exited by the time this section holds the lock, so the class is closed with no new state.

🔧 Fix applied.
1 info still open:

  • ℹ️ docs/design.md:17 - The pool.List entry in design.md (and the comment on inspectListedWorktrees at internal/pool/pool.go:1202-1208) still describes the design from before the round-2 and round-3 fixes. It says only that marker validation and VCS reads share a per-slot lock section. It does not record two invariants those rounds added. (1) Each slot's lease, owner reservation, Destroying, and RecoveryError are re-read from fresh state under that lock (pool.go:1251-1263), and a slot that was removed or reserved for destruction is left out. (2) A slot that would be in-use only because the batched scan found processes in it is re-scanned under its lock (pool.go:1264-1275), because the unlocked batch can catch Git children that another lock holder ran in the slot. A failed re-scan reports the slot unverified. AGENTS.md sends future agents to design.md before they change an area, so without this rationale someone could 'optimize away' the per-slot ReadState or the re-scan and reopen both races. Fix: add one sentence for each invariant to the design.md entry and to the function comment. Behavior does not change.
✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 8 of 9 scenarios driven live against the product
Scenario Result Live Evidence
Running treehouse status on a pool with leased, dirty (nested untracked file with showUntrackedFiles=no, staged rename only, tracked edit), in-use (process reached via the /tmp symlink), you're-here… ✅ pass live status-parity.txt: table output matches and status --json from head and base is IDENTICAL, with GIT_OPTIONAL_LOCKS both set and unset
treehouse status on a 15-slot pool returns faster than the base build ✅ pass live status-timing.txt: BASE about 1290 ms against HEAD about 815 ms mean over 3 interleaved rounds of 8 runs
Adversarial: treehouse status on a slot whose index is stale (core.untrackedCache=true, optional locks enabled) does not rewrite the index or take index.lock, and still reports the slot available ✅ pass live index-not-written.txt: BASE REWRITTEN on both runs, HEAD UNCHANGED on both runs; slot 4 still listed as available
Adversarial: while several agents poll status, another agent's get --lease/return cycles always succeed, reuse the available slot without growing the pool, and see no index.lock errors ✅ pass live concurrency.txt run 1: 0 lifecycle failures, slot 4 handed out 20/20 times, pool size stayed 15, 0 index.lock errors
Adversarial: concurrent status observers (and concurrent get/return) never make an idle slot read in-use from another command's short-lived git child ✅ pass live concurrency.txt run 1: in 29 concurrent status runs on HEAD, no idle slot was reported in-use
Lifecycle commands are no longer blocked for the whole listing: get --lease latency under continuous status polling drops ✅ pass live concurrency.txt run 2: get latency 5–23 s on BASE against about 0.9 s on HEAD; status median latency 6.0 s against 5.2 s
Adversarial: a slot destroyed while a status run is in flight is omitted from that listing, not reported damaged ✅ pass live race-destroy-lease.txt: slots 15 and 14 destroyed mid-listing were OMITTED, slot 13 (destroyed after it was read) showed available, and only the intentionally broken slot 5 was damaged
A slot leased while a status run is in flight is reported from fresh state (leased, with its lease id) ✅ pass live race-destroy-lease.txt: in 5 of 6 staggered runs the concurrent status reported slot 12 as leased with the new lease_id; in the latest-timed run it was reported available
A process seen in a slot by the batched scan but gone by the slot's locked section is reported available, a persisting one stays in-use, and a failed locked re-scan reports unverified ⏸️ untested no The prior payload did not establish a live result for this scenario. Only unit tests covered it (TestListDropsProcessGoneBeforeSlotIsLocked, TestListKeepsProcessStillInSlotWhenLocked, TestListReportsU…
  • Built treehouse-base (bf61797) and treehouse-head (64a7cb2) into a temp dir. Gave them a separate HOME, TREEHOUSE_ROOT and global git config, a bare origin, and a fresh repo
  • Mixed fixture: leased; nested untracked file with status.showUntrackedFiles=no; sleep running in the slot via the /tmp→/private/tmp symlink; caller cwd; broken .git marker; staged rename only; tracked edit. Ran treehouse status and status --json with both builds and diffed the JSON with jq
  • Gave a slot's tracked files an old mtime with core.untrackedCache=true and GIT_OPTIONAL_LOCKS unset. Compared the slot's index sha before and after treehouse status for the base and head builds
  • Ran 8 interleaved treehouse status runs, 3 rounds per build, on a 15-slot pool
  • Four background status --json loops plus 20 get --lease --no-fetch/return cycles. Checked lifecycle failures, which slot get handed out, pool size, nonzero status exits, idle slots reported in-use, and index.lock errors
  • Same contention with 10 cycles, timing each get and each status run for both builds
  • Ran treehouse destroy &lt;slot&gt; --yes and treehouse lease 12 at staggered delays while a status --json was running, and recorded how that status reported the slot
  • go test -count=1 ./internal/pool/ -run 'TestList|TestStatus' -v
  • go test -count=1 ./internal/process/ ./internal/vcs/gitvcs/ -run 'Dirty|FindProcesses|Worktree'
  • go test -count=1 ./cmd/ -run Status
  • GOOS=windows go build ./...
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Keep recovery and state healing locked, then inspect captured entries without holding up lifecycle state changes. Read each process cwd once per listing, preserve untracked-file detection with cheaper Git output, and report failed cleanliness checks as unverified.
@PabloSzx PabloSzx changed the title fix(pool): stop status from holding the pool lock during slow slot inspection perf(pool): batch process scans and shorten listing lock holds Oct 21, 2026
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: thanks, @PabloSzx. I skimmed the diff (no workflow changes, and the only new subprocess is a git wrapper used by a test) and approved fork CI on 98836de so your no-mistakes pipeline can finish. Since this is still a draft, I'll do a full review, including the VISION and contract-class verdicts, once it's marked ready. Heads up: main's Windows test lane is currently over Go's 10-minute per-package budget on its own (42b8812), so a Windows timeout in cmd or internal/pool here may not be your change.

PabloSzx and others added 4 commits October 29, 2026 16:43
Keep batched process inspection outside the pool lock and validate each
slot's marker and registration backlink inside its locked VCS section.
Preserve upstream damaged-slot overrides, cwd filtering, HeldOnlyByCwd,
and quarantine recovery without repeating recovery for each slot.

Report failed batch enumeration with one warning and empty process lists,
while retaining upstream status precedence. Preserve the boolean Git dirty
check flags and optional-index-lock suppression.

Retain upstream pre-checkout acquisition reservations, invalid-marker
destroy refusals, same-path registration healing, reftable HEAD
transactions, installer changes, releases, and valid-backlink fixtures.
@PabloSzx PabloSzx changed the title perf(pool): batch process scans and shorten listing lock holds fix(pool): scan processes once per status listing and narrow lock scope Oct 29, 2026

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants