fix(claude-ops): use type -P for jq prereq check to avoid wrapper shadowing#841
Conversation
…dowing telemetry-upsert.sh and machine-behavior.sh each define a `jq()` wrapper function before checking for the jq binary. `command -v` resolves shell functions ahead of PATH executables, so the prerequisite check passed even with no jq binary installed, letting both scripts fail open (exit 0) instead of the documented exit 4. `type -P` searches PATH executables only, ignoring functions/aliases/builtins, so the guard now catches a missing binary regardless of definition order. Closes #812 Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Claude finished @kyle-sexton's task in 2m 3s —— View job PR Review — fix(claude-ops): use
|
| File | Has jq() wrapper? |
Guard before or after wrapper? | Action |
|---|---|---|---|
machine-behavior.sh |
Yes (L57) | Guard was after | Fixed ✓ |
telemetry-upsert.sh |
Yes (L69) | Guard was after | Fixed ✓ |
fleet-state.sh |
Yes (L81) | Guard is before (L71) | Correctly left alone ✓ |
lane-launcher.sh |
No | N/A — no wrapper defined | command -v jq is safe here ✓ |
machine-behavior.sh also has a command -v gh at L129 inside gh_identity() — no gh() wrapper exists in this script, so command -v is correct there and was rightly left alone.
Test review
Both new regression cases (machine-behavior.test.sh L201–205, telemetry-upsert.test.sh L310–315) use the same sound pattern:
BASH_BIN="$(command -v bash)" # capture bash path before restricting PATH
out="$(PATH="$STUB_BIN" "$BASH_BIN" "$SCRIPT" … 2>&1)"Resolving $BASH_BIN first (while the test's own PATH is still intact) then restricting the child's PATH to stub-only is the correct approach for platform-portable PATH isolation — it sidesteps the shebang lookup problem and guarantees no jq binary leaks through. The assertions check both exit code 4 and the "jq not found" message string, which together pin down the exact failure path.
The PR description's empirical verification (reverting the fix and re-running the test to confirm failure) is a good anti-vacuousness check and worth noting as a model for future regression tests.
No issues found. The fix is correct, minimal, and the tests are meaningful.
|
Claude finished @kyle-sexton's task in 1m 14s —— View job Security Review
No security issues found. This PR is a net security improvement. What was reviewedThe four changed files:
AnalysisThe fix closes a fail-open path. Before this PR, both scripts defined a No new attack surface introduced. Unchanged Test additions are safe. No injection surfaces, no credential exposure, no permission changes introduced by this PR. |
|
Tower merge-sit: merge-drive lane dark ~5.5h (rate-limit hold since ~10:36Z); tower sitting merges per stall protocol. Gate-verified live: CLEAN, 0 unresolved threads. This was generated by AI (control tower, session 6). |
…0; harden usage() (#844) ## Summary Closes the three non-blocking test-coverage/maintenance nits deferred from the claude[bot] review on PR #569 (the `morning-brief` dashboard skill). ## Fix - Added a regression test for the `any=0` telemetry path: a non-empty telemetry-comments array where none carry a `lane:` field (only scope-note comments) — previously only the `null`/no-issue-found case and the populated-with-lanes case were covered. - Added a regression test for `--rec-maxlen 0` (full, untruncated RECOMMENDED preview) — previously the `0 = full` semantics were only exercised implicitly via the `!= "0"` source check, with no assertion that a long recommendation tail survives untruncated. - Hardened `usage()`'s header self-extraction: replaced the hardcoded `sed -n '2,35p'` line range with a sentinel-based `awk` extraction (shebang line skipped, then every comment line printed up to the first non-comment/blank line). The header comment can now grow or shrink without silently truncating or over-running `--help` output. - Bumped `plugins/claude-ops/.claude-plugin/plugin.json` to `0.17.2` (patch — test/maintenance only, no behavior change) and added a matching `CHANGELOG.md` entry. ## Verification Ran the plugin's own test suite locally (bash + jq, both GNU-date and BSD-date-stub paths): ``` $ bash plugins/claude-ops/skills/morning-brief/morning-brief.test.sh ... PASS: [28] rec-maxlen 0 keeps the full tail untruncated PASS: [29] rec-maxlen 0 never inserts an ellipsis PASS: [30] telemetry with no lane-tagged comments reports none found ... morning-brief.test.sh: all 47 cases passed ``` All 47 cases pass (44 pre-existing + 3 new: rec-maxlen=0 truncation-skip, rec-maxlen=0 no-ellipsis, and the any=0 no-lane-comments case). Also manually diffed `--help` output before/after the `usage()` change — byte-identical. Closes #579 ## Related - #579 (this issue) - #569 (origin PR — the `morning-brief` dashboard skill; claude[bot] review surfaced these as deferred, non-blocking nits) ## Note on serialization PR #841 (`fix/812-jq-guard`) is currently open and also touches `plugins/claude-ops/` (a different skill, `lanes`). Opening this as **draft** with `do-not-merge` per the repo's serialization convention for concurrent `claude-ops` changes; this PR's version bump (`0.17.1` → `0.17.2`) may need to be re-based once #841 lands. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Summary
telemetry-upsert.shandmachine-behavior.sheach define ajq()wrapper function (to strip CRLF from native-Windows jq output) before checking for the jq binary withcommand -v jq. Sincecommand -vresolves shell functions ahead of PATH executables, the prerequisite check passed even with no jq binary installed — defeating the guard and letting both scripts fail open instead of the documentedexit 4.Fix: use
type -P jq(and, in thetelemetry-upsert.shloop,type -Pfor bothgh/jq), which searches PATH executables only and ignores functions/aliases/builtins — so the guard now catches a missing binary regardless of definition order.Closes #812
Sites fixed
plugins/claude-ops/skills/lanes/scripts/telemetry-upsert.sh(prereq loop, was L73-78)plugins/claude-ops/skills/lanes/scripts/machine-behavior.sh(prereq check, was L63-66)A third file with a
jq()wrapper,plugins/claude-ops/skills/plugins/scripts/fleet-state.sh, was checked and does not have this bug — itscommand -v jqcheck already runs before the wrapper is defined, so it wasn't touched.Empirical verification
Before (bug reproduced against the actual scripts, jq absent from PATH):
After (same PATH):
Regression tests were also confirmed to actually catch the bug: reverting the one-line
telemetry-upsert.shfix and re-runningtelemetry-upsert.test.shfails the new case (exit 3 with adirname: command not founderror, since the falsely-passed guard let the script proceed further before breaking) — proving the test isn't vacuously passing.Test plan
machine-behavior.test.shandtelemetry-upsert.test.sh: run the script with a PATH containing the gh/git stub(s) but no jq binary anywhere, assert exit 4 and the "jq not found" message.machine-behavior.test.sh(26/26),telemetry-upsert.test.sh(38/38).shellcheckclean on all four modified files.Related
plugins/claude-ops/skills/plugins/scripts/fleet-state.sh— carries the samejq()CRLF-stripping wrapper, but orders itscommand -v jqguard before the wrapper is defined, so it is not vulnerable to this shadowing and is deliberately left unchanged by this PR.🤖 Generated with Claude Code