Repository navigation
Coordination readers refuse in a Claude Code cloud session - #2441
Conversation
In a cloud VM --git-common-dir resolves to the clone's own .git, so seat.ps1, mail.ps1, alloc.ps1 and claim.ps1 would "succeed" into a mefor-coord directory no other session reads. Each now prints one stderr line and exits 3 when CLAUDE_CODE_REMOTE=true. seat.ps1 exempts its two hook paths, -Record and -Prompt, which must exit 0. The SessionStart declare-prompt hook prints a one-line cloud-Builder notice instead of the prompt, stamps nothing, and still exits 0. tests/conftest.py drops an inherited CLAUDE_CODE_REMOTE so the existing pwsh suites keep testing the local path inside a cloud VM. New tests/test_coord_cloud_refusal.py pairs each refusal with a control that takes the normal path.
It hardcoded .venv\Scripts\python.exe, so it failed on a Claude Code cloud VM even with pwsh installed. It now uses .venv/bin/python off Windows, judged by [Environment]::OSVersion so Windows PowerShell 5.1 (no $IsWindows) still reads as Windows, and defaults -Python to python3 there. The install line is unchanged, so both parity tests hold. tests/test_worktree_ensure_venv.py drops its Windows-only gate and builds the fixture's site-packages path per platform, so the ubuntu legs, which carry pwsh, drive the bin/python branch.
Section 5 gains one rule: a Claude Code cloud session is a Builder in its own session, on MEFORORG/MessageFoundry alone and never with a second repository attached, because a multi-repo session loads no project hooks. It opens its PR over REST, since the proxy refuses GraphQL, and hands it to the Lander with the qa label and a PR comment, not mail. Evidence: probe PR 2432 and wiki note 20261018T215116801Z-5lqiqo.
The ubuntu tooling leg errored in the fixture: the template's commit detached background maintenance, whose .git/objects/maintenance.lock vanished while copytree walked it. The template now sets maintenance.auto=false and gc.auto=0, and the per-test copy skips *.lock files.
handoff.ps1, lane.ps1, owner_log.py, tombstone-retired-backlog-allocator.ps1 and hygiene-report.ps1 now print the same one stderr line as PR 2434's four scripts and exit 3 under CLAUDE_CODE_REMOTE=true, path overrides included. Inline copies, not a shared helper: the set spans PowerShell and Python, fixtures copy scripts file by file, and an override bypasses a resolver. tests/test_coord_cloud_refusal.py adds a refusal and a control per script, and a census that fails until every script naming mefor-coord is guarded or classified (user-scope hook, clone-local, reader, mention).
…epeats The /simplify pass: the script label is derived from its path, owner_log.py runs from a cwd with no clone and one remote flag, the text check of the Python copy goes (the behavioural tests already run without pwsh), and the GUARDED comment states the reasons that hold.
CLAUDE.md no longer says every writer refuses: a clone-local lock or cache is still written, and the census says which. handoff.ps1 documents exit 3, the param comments in handoff.ps1 and lane.ps1 name the refusal they mean, and the census tests bytes, so a non-UTF-8 script cannot crash it.
…' into claude/coord-cloud-refusal-rest
In a cloud VM <git-common-dir>/mefor-coord is the VM clone's own and holds nothing, so overlap.ps1, claim-adjudicate.ps1, claim-reconcile.ps1 and fleet.ps1 returned a confident empty answer that looked clean, and presence.ps1 listed only the VM's own config roots. Each now refuses with exit 3 and one stderr line, like the writers. overlap.ps1's exit 3 is already its did-not-finish code, so its consumers read the refusal as no all-clear. presence.ps1 carries a tail of its own, because it reads config roots, not mefor-coord. lock.ps1 stays unguarded. The census moves the four coord readers to GUARDED; each new guard has a refusal test and a control without the variable. PR title: Coordination readers refuse in a Claude Code cloud session
…t-3 prose The fleet.ps1 control asserted exit 0, but fleet exits 2 wherever no ~/.claude*/sessions exists, as on a fresh Windows runner. Each reader's control now gets a fixture USERPROFILE and asserts a line of its normal answer, not only exit 0. overlap.ps1's guard comment claimed every consumer reads the refusal as no all-clear; session-context.ps1 reads stdout only, so the comment now names both kinds, and -TimeBudgetSeconds names exit 3's second meaning.
|
Lander: this PR now CONFLICTS with main. Engine PR 2437, the branch it was stacked on, merged at 16:03:42Z, and GitHub retargeted this PR to main. |
…ader-guard # Conflicts: # tests/test_coord_cloud_refusal.py
|
Lander: ready for you under the qa label. Head 4b3db9c is fae4caf (the reviewed head, xhigh code-review QA line in the body) plus a clean merge of main after #2437 squash-merged; the only conflict was tests/test_coord_cloud_refusal.py, add/add, resolved to this branch's copy because main's copy was byte-identical to #2437's tip. The Windows tooling leg is the one that runs the reader controls. |
|
Rechecked after the main merge (4b3db9c): main's copies of #2437's files equal its final head 283fbd1 (empty diff), and |
|
Lander: enqueued at 4b3db9c, pinned.
|
Was stacked on #2437; that has merged, the PR is retargeted to
main, andmainis merged in (4b3db9c). The diff againstmainis this change's six files only.What
In a Claude Code cloud VM (
CLAUDE_CODE_REMOTE=true),<git-common-dir>/mefor-coordbelongs to the VM's own clone and holds nothing. So the coordination readers, run by hand, returned a confident empty answer that looked clean. Each one now refuses the way the writers do: exit 3 and one stderr line, with nothing on stdout.overlap.ps1[]collision_gate.ps1, which reads the exit code, treats it as no all-clear.claim-adjudicate.ps1claim-reconcile.ps1-Apply, it would hitclaim.ps1's refusal only after a false-clean read)fleet.ps1presence.ps1mefor-coordI chose refusing over printing an explicit "no coordination state" answer because callers that check exit codes already treat a non-zero exit as not clean. An exit-0 answer would read as clean to anything that checks only the code.
Local exit-code contract is unchanged. Without the variable, behaviour is identical. The user-scope hooks that run
overlap.ps1/presence.ps1(collision_gate.ps1,session-context.ps1,announce-session.ps1) never load in a cloud session.mail.ps1's call topresence.ps1comes aftermail.ps1's own refusal. No project-scope hook calls any of these five readers.lock.ps1stays unguarded..claude/settings.jsonis untouched.Tests (
tests/test_coord_cloud_refusal.py)mefor-coordcreated.[],nothing was scanned., fleet'sfenceAvailable True, and presence's fixture session row). The controls point-ConfigRoot/USERPROFILEat fixtures, so they never read the host's registry, and they never callgh.NOT_GUARDEDtoGUARDED.presence.ps1is outside the census, which matches on the directory name, and has its own refusal-line check.Checks run
ruff checkandruff format --checkon the test file: clean.ci.ymllegs: clean (361 / 342 / 1388 / 6 files).test_coord_cloud_refusal.pyplustest_cp1252_console_safety.pygave 142 passed after the review fixes. Before them, the readers' own suites (overlap x4, collision_gate, claim_adjudicate, claim_reconcile, fleet_fetch_clock, presence, session_context_presence, announce_hook, seat_, handoff_, control_char_check) gave 722 passed.test_crypto_inventory_doc.pyplustest_asvs_scorecard.pygave 320 passed. The full suite was not run.needs_windows); Linux runs only the refusal and line tests.QA:
code-reviewskill atxhigh(tag printed:xhigh effort -> 10 inline angles -> dedup (no verify) -> sweep -> <=15 findings) onorigin/claude/coord-cloud-refusal-rest...eec7cffdb, reading docs/REVIEW-STANDARDS.md. It found 5 issues, all fixed in fae4caf:~/.claude*/sessions, so it would have gone red on a fresh Windows runner.session-context.ps1.-TimeBudgetSecondsdoc did not mention exit 3's second meaning.One repair round was used.
/simplifyran first. The first commit message repeats the "consumers read the refusal as no all-clear" wording; this body corrects it.Follow-up, not done here:
collision_gate.ps1's comment "EXIT 3 IS THE BUDGET" still names only the walk budget. This only matters in a cloud session, where that hook never loads.