Gate the Shell Corpus, and Run the Host-Gate Tests CI Never Ran - #664
Merged
Conversation
The validation workflow lints Markdown, workflows, EditorConfig, prose and the JSON spec, and it does not read shell at all. Four scripts are in the tree today and a host-setup layer is arriving, so the code layer of the only language here with no gate is about to grow. The file list comes from `git ls-files` rather than a glob, so a script added later is covered without editing the step. Proven by adding one: an unquoted expansion in a new file fails the step, and removing it passes. Turning the gate on costs one disable. `repo-config/configure.sh` holds a jq program that must stay single-quoted, since `$in` and `$k` are jq's variables and shell expansion would empty both. The file already carries four SC2016 disables in that style and this is the fifth, so the convention is completed rather than introduced. Verified load-bearing by stripping it, which returns the finding. `scripts/test_host_gate.py` ran in no workflow. The self-test step listed five entries and the host-gate tests were not among them, so the 61 cases covering the tighten-only overlay merge were never executed by CI. The comment above that step says every gate in `scripts/` is proven by a case that reintroduces the fault it catches, which was true of four gates out of five. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds CI coverage for the repository’s shell corpus and ensures the host-gate test suite is actually executed in the validation workflow, closing a gap where shell scripts (and scripts/test_host_gate.py) were previously ungated/unrun.
Changes:
- Add a
shellcheckDocker step in the validation workflow, sourcing the script list fromgit ls-files. - Run
scripts/test_host_gate.pyas part of the workflow’s script self-test step. - Add an inline
shellcheckSC2016 disable for the jq filter string inrepo-config/configure.shto prevent an incorrect warning.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| repo-config/configure.sh | Adds a targeted shellcheck disable comment for jq-variable $ usage inside a single-quoted jq program string. |
| .github/workflows/validate-task.yml | Introduces a shellcheck gate for tracked *.sh files and adds the host-gate tests to the existing self-test step list. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Uh oh!
There was an error while loading. Please reload this page.
ptr727 added a commit
that referenced
this pull request
Aug 11, 2026
…ler Ignored (#665) Rolling the safety kit across a fleet needs an answer to *"is this machine current"*, and there was none. [#365](#365) tracks machines by tick, a tick records that someone ran something once, and the kit has changed three times since the earliest one. Its own body admits this: *"a machine ticked below is not necessarily current."* Verifying meant typing three `grep` commands per host and reading them by eye. ## The stamp Every install writes `~/.claude/agent-safety-stamp.json` and prints one pasteable line: ``` server-2 | Debian GNU/Linux 13 (trixie) | hub 31880f0 | payload a1ea65e4e604308f | agent-safety v1, fleet-bootstrap v1 | 2026-08-11T00:48:58Z ``` Host name, host type, the hub commit it came from, a digest of the bytes installed, the marker versions actually found in `CLAUDE.md`, and when. ## `--report` Read-only. Run it from a fresh hub checkout and it compares that machine against that checkout: - **CURRENT** — matches, exit 0 - **STALE** — with the specific reasons, exit 1 - **NOT INSTALLED** — no stamp, exit 2 It returns before creating anything, so reporting on a clean machine leaves it clean. ## Three judgement calls worth stating **Compared on the payload digest, not the commit.** A machine installed from an older commit whose kit bytes never changed *is* current, and calling it stale sends someone to re-run an installer that would write the same file byte for byte. **The digest covers the installed bytes, not the tree state.** A clean commit and a dirty checkout install different content under the same SHA. A dirty install is recorded as dirty, because a stamp that claims a commit identifies the bytes when it does not is the thing this exists to prevent. **Blocks are read back off disk.** Not assumed from what the installer meant to write. A block edited or deleted by hand since the install reports stale, and a start marker without its end does not count as present — the half-written case a presence check reads as success. ## A defect fixed in passing `main()` took no arguments while both wrappers pass `"$@"` through, so every flag was silently discarded. `install.py --help` performed a full install instead of printing usage. It now parses, and an unknown flag exits 2 having changed nothing — asserted by comparing the stamp before and after. ## Verification `test_install.py`, 13 cases, each proving a verdict by reintroducing the state it reports: - Report on a never-installed machine says so **and creates nothing** - A changed payload reports stale; a hand-deleted block reports stale - **The printed remedy actually clears it** — re-running returns CURRENT, so the verdict is not a dead end - A half-written block is not counted as present - `--bogus` is rejected with the stamp unchanged; `--help` prints usage and installs nothing - **Every file the kit deploys is in the digest**, checked in both directions: changing each listed file moves the digest, and every file `install.py` reads is in the list All cases run against a throwaway `CLAUDE_HOME`, never the invoking user's. Wired into the self-test step, because a test that runs nowhere is the defect [#664](#664) just found in another gate. ## Merge order This touches the same self-test block as [#664](#664). Whichever merges second needs a one-line rebase in that list — I will handle it. Follow-up not in scope here: the issue body still carries the manual grep instructions. I will rewrite it to the one-command-per-platform form once this lands, so the instructions describe a flag that exists. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
ptr727 added a commit
that referenced
this pull request
Aug 11, 2026
…ng (#670) Five commits, `1927e9a..56f4d7d`. Nineteen files, +4241/-136. ## What lands - **#664** `b0d0d13` — a shellcheck gate in `validate-task.yml`, with the file list from `git ls-files '*.sh'` so a new script is gated without editing the step. Also wired `scripts/test_host_gate.py` into the self-test step, which was running in no workflow at all. - **#666** `8c6fd27` — `prose_lint.py` chose its rule set from the working directory rather than the scanned repository, so standing in an operational repo and scanning a release repo silently discarded `home-path`, the rule that exists because real paths reached a public comment. - **#665** `e2a99f1` — a host stamp at `~/.claude/agent-safety-stamp.json` plus `--report`, so "is this machine current" has an answer that is not a tick in an issue. Also fixed `install.py` taking no arguments while both wrappers passed `"$@"`, which made `--help` perform a full install. - **#668** `6864a9b` — the two remaining `prose_lint.py` false cleans, fixed as a class. An absolute path argument scoped a `--diff` run to nothing and exited 0, and an untracked file was invisible to both a diff-scoped run and a whole-tree sweep. Every input to a verdict now derives from the repository being scanned, and every run states the scope it read. - **#667** `56f4d7d` — the host bootstrap tooling under `host-setup/linux/`, its `bootstrap.sh` loader, `scripts/test_bootstrap.py`, and the rules the scripts run under. ## Review record Every one of the five closed its Copilot loop on its own pull request. #668 ran five rounds and #667 seven, and between them eighteen findings arrived as suppressed comments rather than as inline threads, thirteen of which were real. Two of those were defects that would otherwise have shipped in the gate this promotion carries: a subtree of new files taking a filesystem walk that applies no ignore rules, and a docstring count that was wrong as well as brittle. ## Consequence worth stating The `GOVERNANCE.md` "Hub-Hosted Tooling" paragraph #667 added makes every carrying repository's copy a past revision once this reaches `main`. That is the ordinary consequence of a canonical moving rather than a defect, but a repository meeting it first as a red audit line will read it as a surprise. HomeAutomation-Config has already re-vendored it by content rather than by bytes, since a byte copy from a CRLF hub into an LF repository rewrites every line to change one paragraph. ## Verified on this head `develop` at `56f4d7d`, in sync with `origin/develop`. Local run of the CI invocations: 223 prose self-tests, the prose gate over 117 files, `repo_gate` (eol, eol-coverage, sha-pin), `spec/validate.py` with 22 cataloged, markdownlint over 45 files, and editorconfig-checker, all clean. Merge as a **merge commit**, never a squash, and without `--delete-branch`: this pull request's head is `develop` itself.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The validation workflow reads Markdown, workflows, EditorConfig, prose and the JSON spec, and it does not read shell at all. Four scripts are in the tree today, and a host-setup layer of standalone bash is arriving, so the one language here with no code-layer gate is the one about to grow.
prose_lint.pyalready covers.shcomments, which makes the gap precise: the comment layer is gated and the code layer is not.Two changes
A
shellcheckstep, pinnedkoalaman/shellcheck:stable, run through docker beside the EditorConfig step it resembles. The file list comes fromgit ls-filesrather than a glob, so a script added later is gated without editing the step.scripts/test_host_gate.pyadded to the self-test step. It ran in no workflow. The step listed five entries and the host-gate tests were not among them, so 61 cases covering the tighten-only overlay merge were never executed by CI. The comment above that step reads "Each gate inscripts/is proven by a case that reintroduces the fault it catches", which was true of four gates out of five. Not run here:host_gate.pyitself, which measures a host against the fleet floors — a runner is a different machine with different needs, and the layering logic is the part worth testing.What turning the gate on costs
One disable.
repo-config/configure.shholds a jq program that must stay single-quoted, because$inand$kare jq's variables and shell expansion would empty both. Double-quoting to satisfy SC2016 is the defect the quoting prevents.The file already carries four SC2016 disables in exactly that style, so this is the fifth and the convention is completed rather than introduced.
Verification
The gate is proven able to fail, not merely observed passing:
repo_gate.py,prose_lint.py, editorconfig-checker.Landing this before the host-setup scripts is deliberate. A gate retrofitted after the code arrives has its first exercise as an argument about someone's work, and the disable here would be reviewed buried in a large move rather than on its own merits.