Skip to content

Cloud refusal for the remaining mefor-coord writers: handoff, lane, owner_log, tombstone, hygiene report - #2437

Merged
wshallwshall merged 9 commits into
mainfrom
claude/coord-cloud-refusal-rest
Oct 20, 2026
Merged

wshallwshall merged 9 commits into
mainfrom
claude/coord-cloud-refusal-rest

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

Follow-up to PR 2434, closing the finding its xhigh review left open: other writers into <git-common-dir>/mefor-coord still succeeded silently in a Claude Code cloud VM, where that directory belongs to the VM's own clone and no other session reads it.

Stacked on PR 2434 (base claude/xenodochial-euclid-bb7971, merged in at f9e7726e6). Land 2434 first. GitHub retargets this one to main when that branch is deleted.

What changed

Five more scripts print PR 2434's refusal line, byte for byte with only the script name changed, and exit 3 when CLAUDE_CODE_REMOTE is true. Each refuses before it reads or writes anything, and also refuses when a path override is passed (the same rule mail.ps1 follows for -MailRoot):

Script What it would have written Override that is also refused
scripts/coord/handoff.ps1 handoffs/ (every verb) -CoordDir
scripts/coord/lane.ps1 lanes/ (every verb) none
scripts/coord/owner_log.py owner-log/OWNER-ITEMS.md MEFOR_OWNER_LOG
scripts/coord/tombstone-retired-backlog-allocator.ps1 alloc/backlog/ -GitCommonDir
scripts/worktree/hygiene-report.ps1 hygiene/ -OutDir

owner_log.py lower-cases the value before comparing, to match PowerShell's case-insensitive -eq. handoff.ps1 documents exit 3 next to its 0/1/2. The CLAUDE.md cloud rule now says "at least" and notes that clone-local locks and caches are still written.

Why inline copies and not one shared resolver

The brief asked for a shared resolver if the fixtures allowed one. I decided against it, for these reasons:

  • No helper is loaded by every guarded script. alloc.ps1, claim.ps1 and the tombstone script dot-source nothing. About ten fixtures copy alloc.ps1 or claim.ps1 alone (for example test_coord_alloc_floor.py, test_claim_shared_registry.py), so adding a dot-source would break them.
  • mail-key.ps1 could hold the guard for the four scripts that load it (seat, mail, lane, handoff). That would leave half the copies in a helper and half inline.
  • There is no shared coord-root resolver. Each script builds the path itself, and a check placed in a resolver would be skipped by -CoordDir, -MailRoot and MEFOR_OWNER_LOG.
  • owner_log.py is Python and could not share a PowerShell helper anyway.

Two things in tests/test_coord_cloud_refusal.py keep the copies equal instead:

  • The text test holds all eight PowerShell copies to one line.
  • A new census fails until every .ps1/.py/.sh under scripts/coord, scripts/hooks and scripts/worktree that names mefor-coord is either guarded or classified.

Inventory: writers into mefor-coord, and why each is or is not guarded

  • Guarded (they can run in a cloud session): seat, mail, alloc, claim (PR 2434), plus the five above.
  • Reached only from user-scope hooks, which install-coordination.ps1 wires and which never load in a cloud session: announce-session.ps1, collision_gate.ps1, mail-drain.ps1 (and mail-claim.ps1 through it), wiki-write-prompt.ps1, seat-record.ps1 (through seat.ps1 -Record), session-context.ps1. mail-watch.ps1 is retired from the wiring.
  • Clone-local, so correct in a VM: lock.ps1, the mutex worktree/new.ps1 and spawn.ps1 take, and overlap.ps1's cache of its own answer.
  • Write only through a guarded script: claim-reconcile.ps1 -Apply writes through claim.ps1 -Release.
  • Project hooks (these do load in the cloud): seat-declare-prompt is guarded by PR 2434. precompact-reprime only reads. role-card-inject, usage-headroom-inject and shell-guard-dispatch write nothing under mefor-coord.
  • Korus scripts/wiki/write.ps1 is in another repository and is not changed here. It already refuses a state root that does not exist, and a fresh cloud clone has no mefor-coord unless something creates one.

Checks run

  • ruff check . and ruff format --check .: clean.
  • mypy, all four ci.yml legs (linux engine, win32 engine, tests on linux, scripts/asvs): clean. scripts/coord/owner_log.py is also clean under --strict.
  • pytest, locally on Windows with pwsh 7: tests/test_coord_cloud_refusal.py 39 passed, every Windows-gated control included. In the earlier run of the touched suites, 1201 passed: handoff report/pointer/clear, lane statement, alloc tombstone, hygiene report, worktree remove, CLAUDE.md citations/settings contract, tooling partition, ci tooling gate, cp1252, not-deployed, crypto inventory, licence header, glyph. After the review fixes, 264 passed in a narrower rerun.
  • No new test file, so tests/tooling_manifest.txt is unchanged (the test file was already listed by PR 2434).
  • Not run here: the ubuntu tooling leg. The refusal tests and the census run there. The Windows-gated controls skip there.

QA

QA: code-review skill at level xhigh (skill tag printed: xhigh effort -> 10 inline angles -> dedup (no verify) -> sweep -> <=15 findings), against docs/REVIEW-STANDARDS.md. It reported 10 findings, fixed 6 in 277913f2f, and skipped 4:

  • The hygiene-report.ps1 refusal still names mefor-coord when -OutDir is given. Skipped because the brief requires the line to stay identical. The comment above the guard gives the real reason.
  • Readers such as overlap.ps1, claim-adjudicate.ps1 and fleet.ps1 return a confident empty answer in a cloud VM. This is outside the writer scope and is proposed as a follow-up.
  • Two cleanup items: the extra scripts/worktree copy in the template, and _owner_log repeating _run's subprocess.run keywords.

/simplify ran first (b827f5eeb).

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.
@wshallwshall wshallwshall added the qa Builder QA record posted; not a merge gate label Oct 19, 2026
Base automatically changed from claude/xenodochial-euclid-bb7971 to main October 19, 2026 22:23
@wshallwshall

Copy link
Copy Markdown
Collaborator Author

Lander: this PR now CONFLICTS with main. Engine PR 2434, the PR it was stacked on, squash-merged at 22:23:24Z (d18836b), and GitHub retargeted this PR to main. Please merge main into the branch, or rebase it onto main, then post a QA line that names the new head. That counts as the handover. The Lander enqueues once the required contexts are green.

…fusal-rest

# Conflicts:
#	CLAUDE.md
#	tests/test_coord_cloud_refusal.py
@wshallwshall

Copy link
Copy Markdown
Collaborator Author

Brought up to date with main after PR 2434 squash-merged (d18836b0d). Merge commit 283fbd1e7; no force-push, and every reviewed commit is still on the branch.

  • Two conflicts, both add/add from 2434's squash: CLAUDE.md and tests/test_coord_cloud_refusal.py. Main's copies of every file 2434 touched are byte-identical to 2434's final head f9e7726e6, which this branch had already merged, so I took this branch's side for both.
  • git diff origin/main now shows only this PR's own seven files: CLAUDE.md, handoff.ps1, lane.ps1, owner_log.py, tombstone-retired-backlog-allocator.ps1, hygiene-report.ps1, and tests/test_coord_cloud_refusal.py. Nothing that was reviewed changed content.
  • Rerun after the merge: ruff check and ruff format --check are clean, and all four ci.yml mypy legs are clean. pytest over the touched suites (cloud refusal, handoff, lane, tombstone, hygiene report, worktree remove/ensure-venv, the CLAUDE.md and tooling guards): 1211 passed on Windows.
  • The app's own base-sync refused this merge on a pinned-origin check for .claude/settings.json, so I merged with git directly. That file arrives exactly as main has it.

@wshallwshall

Copy link
Copy Markdown
Collaborator Author

Lander: enqueued at 283fbd1, pinned. The QA line in the body (code-review at xhigh, tag printed) covers 277913f. The head merges main by a hand resolution, which the Lander reviewed by remerge-diff: it takes this PR's side throughout, and nothing from main is lost. The open findings are filed as vault #3894 in open vault PR 2550 (ledger pass 12), graded as changing no behaviour against main and weakening no control. That grade is a Lander decision under the owner's Driver delegation. All 9 live required contexts are SUCCESS on this head.

@wshallwshall
wshallwshall added this pull request to the merge queue Oct 20, 2026
Merged via the queue into main with commit f09f7c5 Oct 20, 2026
50 checks passed
@wshallwshall
wshallwshall deleted the claude/coord-cloud-refusal-rest branch October 20, 2026 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

qa Builder QA record posted; not a merge gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant