Skip to content

Gate the Shell Corpus, and Run the Host-Gate Tests CI Never Ran - #664

Merged
ptr727 merged 1 commit into
developfrom
feature/shellcheck-gate
Aug 11, 2026
Merged

Gate the Shell Corpus, and Run the Host-Gate Tests CI Never Ran#664
ptr727 merged 1 commit into
developfrom
feature/shellcheck-gate

Conversation

@ptr727

Copy link
Copy Markdown
Owner

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.py already covers .sh comments, which makes the gap precise: the comment layer is gated and the code layer is not.

Two changes

A shellcheck step, pinned koalaman/shellcheck:stable, run through docker beside the EditorConfig step it resembles. The file list comes from git ls-files rather than a glob, so a script added later is gated without editing the step.

scripts/test_host_gate.py added 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 in scripts/ 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.py itself, 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.sh holds a jq program that must stay single-quoted, because $in and $k are 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:

  • The disable is load-bearing. Stripping it returns the finding; restoring it clears.
  • A new script is caught. Adding a file with an unquoted expansion takes the list from four entries to five and fails the step with SC2034, SC2086 and SC2154. Removing it passes.
  • The corpus is otherwise clean. All four existing scripts pass unmodified.
  • Full local suite green: shellcheck, all six self-tests, actionlint, 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.

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>
CopilotAI lite review requested due to automatic review settings August 11, 2026 00:46

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 shellcheck Docker step in the validation workflow, sourcing the script list from git ls-files.
  • Run scripts/test_host_gate.py as part of the workflow’s script self-test step.
  • Add an inline shellcheck SC2016 disable for the jq filter string in repo-config/configure.sh to prevent an incorrect warning.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

FileDescription
repo-config/configure.shAdds a targeted shellcheck disable comment for jq-variable $ usage inside a single-quoted jq program string.
.github/workflows/validate-task.ymlIntroduces 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.

@ptr727
ptr727 merged commit b0d0d13 into developAug 11, 2026
7 checks passed
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.
@ptr727
ptr727 deleted the feature/shellcheck-gate branch August 16, 2026 03:15
Sign up for freeto 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

@ptr727