hook: lint agent-authored shell for $? read after a pipeline — 4 instances, 3 agents, 1 day, all near-misses
#955
Open
opened 2026-07-30 15:58:33 +00:00 by Mos
·
0 comments
No Branch/Tag Specified
main
remediation/state
feat/rm-02-gate-registry
fix/rm-01-reproducible-checkout
remediation/mission-setup
fix/hygiene-inert-format-gate
fix/1019-queue-guard-stdin
feat/mos-ste-writing-standard
fix/1007-suite-hermeticity
fix/991-comment-url-scheme-normalise
feat/push-guard-null-case-verification
mos-comms-live
docs/heartbeat-framework-layering-ms-lead
feat/869-c4-version-coupling
feat/869-c2-install-ordering-guard
feat/869-c5-doctor-activation-check
feat/per-agent-gitea-identity
fix/875-belongs-case-insensitive-slug
fix/ci-queue-wait-404-branch-absent
feat/869-c1-activation-probe
feat/869-c3-broker-supervisor
fix/865-tea-cli-comment-invocation
feat/glpi-skills
fix/860-deflake-mutator-lease-gate
fix/850-detect-platform-port-normalization
fix/856-worktree-deps-preflight
fix/835-pr-review-approve-reject-comment-flag
fix/848-truthful-evidence
fix/812-pr-review-comment
fix/849-recovery-runtime-fixture-race
docs/758-ledger-m5-001-sync
feat/834-tc-server-side-doc
feat/833-constrained-recovery-command
feat/827-gate0-probe
governance/gate0-probe3-amendment
fix/795-codex-pr-diff
fix/795-ci-base-jq
fix/795-ci-base-git
feat/791-pr3-fleet-regen
feat/791-pr2-snapshot-restore
fix/807-glpi-206
fix/808-agent-send-false-sender
feat/791-upgrade-config-protection
feat/790-mosaic-yolo-claudex-pr2
feat/790-mosaic-yolo-claudex
feat/758-v1-v2-migrator
fix/766-exact-fleet-comms
test/758-reconciler-lifecycle-gates
docs/771-kbn101-db-role-split
test/758-example-profile-dispositions
feat/758-shared-role-resolution
feat/mos-logical-identity-fencing
feat/769-kbn100-unified-schema
docs/753-kbn010-threat-gate
feat/758-roster-v2-compiler
feat/756-official-discord-plugin
docs/758-fleet-config-management
fix/mos-option2-qualification-format
docs/issue-758-m0
docs/mos-option2-qualification
mos-comms
feat/tess-interaction-agent
fix/tess-docs-format
next
draft/mosaic-platform-prd
fix/installer-provider-gate-and-local-gateway-redis
release/mosaic-cli-0.0.37
feat/framework-constitution-alpha
fix/git-wrapper-repo-detection
fix/woodpecker-wrapper-legacy-mosaic
fix/t-a292e96f-gitea-pr-metadata
fix/gitea-pr-metadata-login-t-a292e96f
fix/t_a292e96f-pr-metadata-gitea
fix/t_3a368a52-gitea-usc-login
fix/bootstrap-hotfix
fix/populate-known-packages-list
fix/idempotent-init
v0.0.39-alpha
mosaic-v0.0.31
fed-v0.2.0-m2
fed-v0.1.0-m1
mosaic-v0.0.29
mosaic-v0.0.28
mosaic-v0.0.27
mosaic-v0.0.26
mosaic-v0.0.25
mosaic-v0.0.24
v0.2.0
v0.1.0
v0.0.8
v0.0.7
v0.0.6
v0.0.5
v0.0.4
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: mosaicstack/stack#955
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The defect
$?after a pipeline is the last element's exit status. So every one of these reports the status ofhead,tail,grep, orjq— never the command under test:Why this warrants a mechanical check rather than a rule
Four instances in a single day, across three different agents. Two of them nearly became filed defects against correctly-behaving tools; one reached a published claim that had to be retracted.
The mechanism is what makes discipline insufficient:
The pipe gets added because something looks wrong — which is precisely the moment the exit status matters most. The idiom and the hazard have the same trigger.
Every instance was caught by the same accident, and it does not generalise
In all four cases the rescue was identical:
rc=0contradicted visible error text on screen. That is luck of contradiction, not method — and it fails exactly where it is most needed:None of the four would have been caught in that case. The observed catch rate is an artifact of the commands happening to be chatty.
Proposed check
A lint over agent-authored shell flagging a pipeline into
head/tail/grep/jq/sed/awkfollowed by a read of$?— whether asecho $?,rc=$?, or[ $? -eq 0 ].All four instances match that pattern syntactically. This is a PreToolUse-shaped check: cheap, no execution required, no false-negative risk from runtime state.
Complementary standing rule (for the cases a lint cannot see)
Never report an exit status observed through a pipe. If an
rcclaim is going into a durable record — an issue body, a commit message, a merge note — re-measure it unpiped first.Suggested remediations for flagged code
cmd | tail -5; echo $?cmd > /tmp/out 2>&1; rc=$?; tail -5 /tmp/outcmd | grep -q xset -o pipefail, or capture then testAcceptance
$?-read forms.cmd; rc=$?; echo "$out" \| tail(rc read before the pipe) must not fire — otherwise the check is a blanket ban on pipes and will be disabled.Filed as a missing mechanical check, not as three agents needing to be more careful. Operator-agnostic; contains no operator-specific context.