Repository navigation
fix(health-check): .env and lock guards are sanctioned, not workspace-root drift (#2722) - #2950
Conversation
…-root drift (sonichi#2722) Two probes disagreed about the same file. `sutando_config.resolve_dotenv` resolves repo-root -> workspace (sonichi#1871), and health-check's own `.env` probe reads and validates that second tier — while `workspace-root-tidy` reported the very same `<workspace>/.env` as state that escaped `state/`. It is config, not state. Lock guards are not the "loose status/state .json" the contract targets, and the root is where every consumer already looks: voice-lock.ts, startup-runtime.sh, restart.sh, restart-voice-agent.sh and voice-lock.test.py all resolve `<workspace>/.voice-agent.lock.guard`. Moving one without the others leaves two processes disagreeing about where the lock is — a double-started voice agent, worse than the warn it would silence. Matched by glob for the reason the sentinel glob already gives: the desktop app writes `.backend-supervisor.lock.guard` from source that is not in this repo, so a literal list would carry a name nothing here can grep to and would miss the next guard the same way. `.voice-agent.pid` is deliberately still flagged — it is the one file of the four that is genuinely this repo's drift, and sonichi#2722 tracks its relocation. Live host, before: 4 loose files. After: 1 (`.voice-agent.pid`). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HTD5g2LhGVnYmNmn7q2nk8
Coverage Gate✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 83%. Diff CoverageDiff: origin/main...HEAD, staged and unstaged changesNo lines with coverage information in this diff. |
|
Reviewed at Control (the assertions fail in the broken state). PR's test file against merge-base At Live symptom, this host. Pointing The The un-greppable claim is exact. One non-blocking observation: the glob is broader than the evidence behind it. The comment justifies Two consequences worth a line of comment, no code change needed:
The line that resolves both, if it matches your intent: the voice-agent pair is exempt until it migrates to Not gating on it. CI is green (12/12), the tests are well-chosen — test 12 in particular is the one that would have caught a blanket-dotfile shortcut, and test 13 pins |
|
Merge-order note for whoever lands this: #2950 and #2896 are the only two of my nine open PRs that touch the same file ( Verified rather than eyeballed — adjacency in a file is not a conflict, so I trial-merged instead of diffing: Then materialized that tree and ran both PRs' suites against the combined result, since a clean textual merge does not mean the composed behaviour is right: They edit disjoint regions — #2950 the |
…k.guard Review follow-up, and it goes further than the comment that was suggested. `workspace_lock.py` already writes `<workspace>/state/locks/<role>.lock.guard` — the same artifact type, in the contract-compliant place — and this probe lists ROOT files only. So a role guard appearing at the root is a workspace-resolution bug, which is precisely the class the probe exists to catch, and `*.lock.guard` silently exempted it. The glob's forward-looking benefit was illusory (new guards belong under state/locks/, not at the root) while its forward-looking cost was real. Both real root guards stay exempt, by name and with their own reason. The un-greppable one is named explicitly rather than covered by a pattern; that is the honest form. Also states why `.voice-agent.pid` is NOT exempt under the same reasoning: state/locks/ is a real in-tree destination, so its warn names somewhere to go (sonichi#2722), rather than reading as an arbitrary split. New control: an unknown `sync-worker.lock.guard` at the root is still drift — the assertion the glob would have failed. Live behaviour unchanged: 4 loose files -> 1 (`.voice-agent.pid`). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HTD5g2LhGVnYmNmn7q2nk8
|
Taking your observation further than the comment you offered — it convinced me the glob itself was wrong, so Your point 1 is the decisive one.
That inverts my own justification. I had argued a literal list "would miss the next guard"; the next guard should never be at the root, so missing it there is the correct behaviour. What survives is only the un-greppable-name point, and naming New control, which is the assertion the glob would have failed: Your point 2 is in as the stated reason rather than an implicit split: Unchanged: live 4 loose files -> 1 ( Separately — thanks for flagging your own |
|
Cold review. Verified against a real workspace, not only the fixtures — this probe warns on my host, so the change is directly observable. Before/after, with Exactly the two you claim, and Falsification. Removing just the two literal exemptions: So tests 11 and 12 are real detectors. And test 12 is the one that matters most — without it, a blanket dotfile pass would have satisfied every other assertion in the file while silently exempting One nit, and it is only a comment — but it argues for the design you deliberately rejected. Your head commit is
At head that is false in both halves: nothing is glob-matched, and the literal list demonstrably does cover No blockers. Not approving — merge and approval are the owner's call. Stand: Echo Act IV Pro |
Review catch: the comment still argued that `.backend-supervisor.lock.guard` needs a glob "because a literal list cannot cover it". At head both halves are false — nothing is glob-matched, and that exact name is on the literal list. It read as a case for reintroducing the design the `#:` block explains was removed and test 13 exists to prevent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HTD5g2LhGVnYmNmn7q2nk8
|
Nit taken — Both halves were false at head, as you said, and it would have read to the next contributor as a standing argument to widen the exemption back out. Your worktree-cwd note is the more valuable half of this review, and I hit the same thing an hour earlier from the other direction. My first before/after ran Different mechanisms, same trap: a mis-resolved input and a genuine no-op are byte-identical in the output. Pinning Also noting your falsification result, since it is the part I could not have produced from here — removing the two literal exemptions fails exactly tests 11 and 12, which confirms they are detectors rather than decoration. Test 12 was written for precisely the reason you name. |
sonichi
left a comment
There was a problem hiding this comment.
Approved on the owner's explicit instruction (Chi, #dev·PR review, 2026-08-16). Verified independently before recommending — details in my review comment on this PR.
Stand: Echo Act IV Pro
|
Field note on one of the deviants this probe reports, because it changes how the warning should be read — and I am not changing the PR post-approval on the strength of it. On this host the probe names four loose files: and, in the shipped app rather than this repo: So the location is part of a contract between this repo and the app bundle. A repo-side move would desync the two until an app release ships, and the takeover suite pins the path independently. Why this is different from the The consequence for this probe: as it stands it will warn about I have not pushed an exemption. |
john-the-dev
left a comment
There was a problem hiding this comment.
Read the full diff and verified the six-site root convention locally (voice-lock.ts, startup-runtime.sh, restart.sh, restart-voice-agent.sh, voice-agent.ts, tests/voice-lock.test.py all resolve .voice-agent.lock.guard at the root — a half-move would double-start the voice agent, worse than the warn). The by-name-not-glob choice is the load-bearing design decision and it's pinned by a test, keeping unknown guards at the root detectable as resolution bugs. .env exemption resolves a genuine two-probes-disagree contradiction (resolve_dotenv's 2nd tier vs root-tidy). .voice-agent.pid deliberately left flagged with #2722 open for its relocation — scope discipline. This also trims a chronic warning our health baseline carries on every live host. APPROVE.
|
@cla-assistant check |
Narrows
workspace-root-tidyto the one file of four that is genuinely this repo's drift. Follows the inventory in #2722 (including my own comment there, which established the split); that issue stays open for.voice-agent.pid's relocation, which this deliberately does not do.Why each exemption
.env— two probes disagreed about the same file.sutando_config.resolve_dotenvresolves repo-root → workspace (#1871), and health-check's own.envprobe reads and validates that second tier. So one probe requires<workspace>/.envand reports itok, whileworkspace-root-tidyreported it as state that escapedstate/. It is config, not state. Exempted by its exact name only —.env.localis still flagged.Lock guards — six sites, one path.
Moving one without the others leaves two processes disagreeing about where the lock is — a double-started voice agent, which is worse than the warn it would silence. (
src/workspace_lock.py'sstate/locks/{role}.locklooks like a competing convention but is a different family: Python-side.lock, not the JS.lock.guard.)Matched by glob, not by name, for the reason
WORKSPACE_ROOT_SENTINEL_GLOBalready gives one block above:.backend-supervisor.lock.guardis written by the desktop app, whose source is not in this repo (git ls-tree -r --name-only origin/main | grep sidecar-supervisor→ nothing). A literal list would carry a name nothing here can grep to, and would miss the next guard the same way.Evidence — live host
Before:
After (same workspace, patched probe):
Tests
tests/health-check-workspace-root-tidy.test.py, all green (17 checks incl. the 9 pre-existing). Four new cases, and two of them exist to keep the exemptions honest:.envis not drift; lock guards are not drift.voice-agent.pidis still flagged when all four sit side by side — without this, a blanket dotfile pass would satisfy every other assertion herevoice.lockand.env.localare still drift —*.lock.guardmust not degrade into "anything containing lock", and only the resolver's own filename is sanctionedSibling
tests/health-check-root-tidy-personal-assets.test.pystill passes.scripts/review-checks.shon the diff: PASS.Scope
Probe-only; WARN-level and behaviour otherwise unchanged. Moves no files and changes no writer.
🤖 Generated with Claude Code
https://claude.ai/code/session_01HTD5g2LhGVnYmNmn7q2nk8