Skip to content

chore(tooling): shellcheck gate, gitleaks filename exemption, and orchestration observations - #99

Merged
inkatze merged 8 commits into
mainfrom
worktree-orch
Aug 6, 2026
Merged

chore(tooling): shellcheck gate, gitleaks filename exemption, and orchestration observations#99
inkatze merged 8 commits into
mainfrom
worktree-orch

Conversation

@inkatze

@inkatze inkatze commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Summary

Side-effects of orchestrating specs/dev-services Task 1: two tooling fixes the
run forced, and six observation fragments recording what it broke on. No spec
work — that is #98.

Tooling

  • shellcheck wired in (9afa7e3). The repo lints YAML and Ansible and
    scans for secrets, but its 13 tracked shell scripts had no static analysis.
    Pinned in mise.toml, wired into lefthook.yml. --severity=warning rather
    than the default, because the tracked scripts carry deliberate info-level
    SC2015 notes (A && B || C) in the *-test.sh harnesses that would block
    every commit; warning-level is green repo-wide today, so the gate catches
    regressions instead of demanding a cleanup first. The one warning-level
    blocker was module-setup.sh, where SC2154 fires on initdir/moddir/
    systemdsystemunitdir — dracut's module API, injected by dracut, so a false
    positive, silenced file-wide with the reason inline.

  • mise.local.toml exempted from the hostname rule (f46cd08). Adding it
    to .gitignore was blocked by internal-hostname matching the literal
    mise.local inside the filename. This is the fourth member of the
    repo-owned-filenames exemption class feat(spec): remote-shell kickoff sign-off #97 introduced, not a new precedent.
    Anchored like its siblings and verified both directions: mise.local.toml
    passes clean, buildbox-mise.local still fires.

Observations (six fragments)

Five are planwright defects found by tripping over them, one is a scope note.

Fragment What
worker-settings-deny-dash-c-blindspot Security. Every deny rule in planwright's shipped worker-settings.json is a git <subcommand> prefix, so none match git -C <path> <subcommand>. A worker running git -C /path push --force evades every force-push, merge, amend and reset guardrail while the deny block appears to protect them. Worse, an adopter fixing the allow-side matching gap with a broad Bash(git -C:*) would silently disable the whole block.
worker-settings-hook-never-fires The auto-approve hook is wired as "${CLAUDE_PLUGIN_ROOT}"/scripts/worker-command-guard.sh, which is not interpolated when the fragment is passed via --settings. The hook silently never fires and an unattended worker deadlocks on its first compound command with no diagnostic.
streamjson-recover-hangs-silently fleet-streamjson.sh recover writes a zero-byte initial message, so a resumed worker blocks on stdin forever with no turn to take. status reports running, the journal shows no blocks, nothing reports the stall. Observed 36 minutes of silence read as healthy progress.
worker-inherits-lc-all-c Both fleet-streamjson.sh and fleet-dispatch-env.sh export LC_ALL=C, which the launched worker inherits, so project CI runs in the C locale. ansible-lint dies on ansible-config dump; the same lint is green in an ordinary worktree on the same commit. Fails only inside the worker, so the human never reproduces it.
fleet-home-env-gap CLAUDE_PLUGIN_DATA is not exported into the tool environment, so every fleet-*.sh fails to resolve its cross-spec home. Fixed here by the machine-local mise.local.toml.
dev-services-consumer-acceptance Scope note: the operator's real acceptance test (running tecpan over TLS bound to 0.0.0.0) is outside this bundle — tasks.md puts both remote reachability and consuming-project configuration Out of scope, and both services bind loopback. Seed for a successor bundle.

Also here

origin/main was merged in (7e42d3a) because #97 landed while this branch was
in flight and had advanced .gitleaks.toml past this branch's base. Merge, not
rebase.

Verification

lefthook run pre-commit clean (shellcheck, yamllint, gitleaks, syntax-check,
ansible-lint). mise run lint clean. The gitleaks change verified in both
directions rather than assumed.

Not done here

The .gitignore entry and the exemption that unblocks it are included; the
identifier file that would make #98's guard live is not — that needs the
private identifier set, which is the operator's and is deliberately absent from
every artifact an agent can read.

inkatze added 8 commits August 5, 2026 13:57
The repo lints YAML and Ansible and scans for secrets, but its 13 tracked
shell scripts had no static analysis at all. Surfaced when a dev-services
Task 1 worker reached for shellcheck to check the scripts it was writing
and found the repo did not ship it.

Pinned in mise.toml alongside the other tools, and wired into lefthook so
it applies to every shell script rather than one task's.

--severity=warning rather than the default: the tracked scripts carry
info-level SC2015 notes (the deliberate 'A && B || C' assertion idiom in
the *-test.sh harnesses) which would block every commit. Warning-level is
green across the repo today, so the gate catches regressions instead of
demanding a cleanup first.

The one warning-level blocker was module-setup.sh, where SC2154 fires on
initdir/moddir/systemdsystemunitdir. Those are dracut's module API,
injected into the script by dracut itself, so the finding is a false
positive and is silenced file-wide with the reason recorded inline.

Claude-Session: https://claude.ai/code/session_01F8oKrXASMQSXMVjSvuJpn9
Adding `mise.local.toml` to .gitignore was blocked by the internal-hostname
rule, which matched the literal `mise.local` inside the filename. The rule
cannot tell a repo-owned path from a machine name, which is the exact case
#97 already carved out a second exemption class for; this is its fourth
member, not a new precedent.

Anchored like its siblings, so the backstop still catches a real host:
verified that `mise.local.toml` passes clean while `buildbox-mise.local`
still fires.

The file itself is the machine-local mise env at the repo root, carrying
this host's absolute CLAUDE_PLUGIN_DATA and CLAUDE_PLUGIN_ROOT so
planwright's fleet scripts can resolve their state home. Untracked for the
usual reason: every value in it is specific to one machine.

Claude-Session: https://claude.ai/code/session_01F8oKrXASMQSXMVjSvuJpn9
Every /orchestrate dispatch writes a timestamped marker under
specs/<spec>/.orchestrate/, which is runtime state by design — the tower
rebuilds it from git and process evidence rather than reading it back from
a commit. Untracked but unignored, it left the working tree dirty after
every run and showed up as an uncommitted-change warning when opening a PR.

Claude-Session: https://claude.ai/code/session_01F8oKrXASMQSXMVjSvuJpn9
@inkatze
inkatze marked this pull request as ready for review August 6, 2026 23:29
@inkatze
inkatze merged commit a960273 into main Aug 6, 2026
1 of 2 checks passed
@inkatze
inkatze deleted the worktree-orch branch August 6, 2026 23:29
Sign up for free to 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.

1 participant