Repository navigation
Fleet conformance drift: non-standard workflow names, unmanaged inline pin, missing workflows, ref/secret gaps across 27 repos #126
Description
Full-fleet audit of all 27 non-fork smartwatermelon repos, run while shipping part 1 of #123. The advisory fix there is scoped to this repo's two reusable workflows; this issue covers everything the fleet needs beyond that.
Why this wasn't caught earlier: the existing conformance checks search for repos containing claude-blocking-review.yml. Any repo with a differently-named or absent workflow is invisible to that query — it only measures conformance among repos that already conform. That's the same blind spot that let the 2026-04-02 standardization report complete while leaving two repos on the old filename.
Full audit
| Repo | blocking-review | assistant | token |
|---|---|---|---|
.github |
NONE | NONE | missing |
archive-resolver |
v3.1.1 | NONE | missing |
claude-code-workflows-agents |
OLD-NAME | NONE | missing |
claude-config |
v3.1.0 | NONE | ok |
claude-wrapper |
v3.1.0 | 9615f93 (raw SHA) |
ok |
crazy-larry |
v3.1.0 | @v3 |
missing |
dev-env |
v3.1.0 | NONE | ok |
dotfiles |
v3.1.0 | c30bec1 (raw SHA) |
ok |
dumbify |
v3.1.0 | v3.1.0 | ok |
github-workflows |
v3.1.0 | @v1 |
ok |
homebrew-tap |
v3.1.0 | @v3 |
missing |
lock-sync |
v3.1.0 | NONE | ok |
mac-dev-server-setup |
v3.1.0 | @v3 |
ok |
mac-server-setup |
v3.1.0 | @v3 |
ok |
personify |
v3.1.0 | @v3.0.0 |
ok |
pr-review |
v3.1.0 | @v3.0.0 |
ok |
projectinsomnia |
v3.1.0 | @v3 |
ok |
qwen-sidebar |
v3.1.0 | @v3 |
ok |
repo-template |
OLD-NAME | @v3 |
missing |
scripts |
v3.1.0 | NONE | ok |
slack-mcp |
v3.1.0 | @v3 |
ok |
smartwatermelon-marketplace |
v3.1.0 | @v3 |
missing |
spokane-snow |
v3.1.0 | NONE | missing |
superpowers |
NONE | NONE | missing |
superpowers-marketplace |
NONE | NONE | missing |
swift-progress-indicator |
v3.1.0 | @v3 |
missing |
x-thread-reader |
NONE | NONE | ok |
1. claude-code-workflows-agents bypasses the reusable workflow entirely
Highest priority. Its claude-code-review.yml does not call this repo's reusable workflow at all — it inlines its own job with its own pin:
uses: anthropics/claude-code-action@4481e6d3c7bbb88db2a928ca3444c536f589c7c1 # v14481e6d3 = v1.0.131. That is past the GHSA-8q5r-mmjf-575q patch line (1.0.74), so it is not exposed to that specific advisory — but it is 62 releases stale and, critically, completely outside the fleet's update path. Repointing floating tags does nothing for it. Any future advisory in claude-code-action requires a separate manual fix here.
This also invalidates an assumption in #123: the blast-radius analysis there treats every consumer as inheriting the pin through the reusable workflow. This repo doesn't.
It also SHA-pins actions/checkout@34e1148 with a # v4 comment — the same ref-version-mismatch as item 3 of #123.
2. Old claude-code-review.yml filename survives in 2 repos
repo-template and claude-code-workflows-agents still use the pre-2026-04-02 filename. The migration was recorded as complete across all repos; it wasn't.
repo-template matters disproportionately — it is the template new repos are created from, so every future repo inherits the non-standard name and the floating @v3 ref.
3. Three repos have no workflows at all
superpowers, superpowers-marketplace, x-thread-reader have no .github/workflows directory. No blocking review, no assistant.
4. .github org repo has only claude.yml, fully unpinned
uses: anthropics/claude-code-action@v1
uses: actions/checkout@v7Both floating major tags, no SHA pin — contrary to this repo's documented pinning policy. Since smartwatermelon/.github supplies org-level defaults and workflow templates, non-conforming content here propagates.
5. claude-assistant ref drift is much worse than blocking-review
blocking-review is uniform (@v3.1.0 everywhere except the v3.1.1 pilot). The assistant is not:
- Raw commit SHAs of this repo —
claude-wrapper→9615f93(2026-08-07),dotfiles→c30bec1(2026-04-28, ~4 months stale). Pinning a reusable workflow to a bare SHA defeats the semver-tag convention inREADME.md:207and is invisible to any tag-based rollout. - Split across major lines — most repos use
@v3, butgithub-workflowsitself uses@v1. Per claude-code-action pinned to v1.0.70 (GHSA-8q5r-mmjf-575q, patched in 1.0.74) — live in ~30 consumer repos #123 these are separate release lines carrying separate fixes. - Assorted —
@v3.0.0,@v3.1.0,@v3, and 8 repos with no assistant caller at all.
6. Five repos run blocking-review without CLAUDE_CODE_OAUTH_TOKEN
homebrew-tap, crazy-larry, spokane-snow, swift-progress-indicator, smartwatermelon-marketplace (plus archive-resolver, being handled separately).
The workflow fails its token precondition and exits 1, so the check goes red — visible, not silent. But worth noting the fallback at claude-blocking-review.yml:674 can exit 0 with "Defaulting to PASS (infrastructure issue)" when no verdict is found and the Claude step didn't fail. Any path reaching that branch yields a green required check with no review behind it. Worth confirming all five fail closed.
The secret can only be set by a human via /install-github-app.
Suggested sequencing
claude-code-workflows-agents— migrate to the reusable workflow so it rejoins the fleet update path.repo-template— rename to the standard filename and pin@v3.1.0+, so new repos stop inheriting drift.- Assistant ref normalization — replace both raw-SHA pins, settle the
@v1vs@v3split. - Missing workflows — add the standard set to the 3 empty repos and fix
.github. - Tokens — human step, 5 repos.
Preventing recurrence
The audit needs inverting: enumerate all non-fork repos and assert each has a conforming workflow, rather than searching for repos that already have one. A repo missing the workflow entirely is precisely the case the current check cannot see.
Refs #123
Activity
twistedmelonman commentedon Aug 17, 2026
Carried over from #123: ship a
zizmor.ymlas part of the standard set (was item 5)#123 is closed — items 1-4 are resolved. Its item 5 is consumer-side policy work and belongs here with the rest of the fleet conformance effort.
The problem
zizmorflags two things in every consumer repo that adopts the standard workflow set:unpinned-usesonuses: smartwatermelon/github-workflows/...@v3.1.1— reusable workflows here use a repo-wide semver tag convention, not per-action SHA pinning. Stated explicitly atREADME.md:207.excessive-permissionson the workflow-levelpermissions:blocks — which are required by the reusable workflows being called; the run fails withstartup_failurewithout them.Both are deliberate, documented architecture.
zizmoris disagreeing with the design, not finding a defect. They reproduce identically across repos — #123 measured 19 byte-identical findings each inpersonify,pr-review, anddumbify.Why it matters more than it looks
Every repo adopting the standard set inherits a pre-commit hook that blocks on these, and the only workaround is
SKIP=zizmorper commit. That trains people to bypass the check that would have caught item 1 of #123 in the first place.This is not hypothetical — during the #123 work I hit exactly this and had to use
SKIP=zizmoron two separate commits in this repo, each time confirming by hand that the blocking findings were pre-existing and unrelated. That manual verification is precisely what people stop doing once skipping becomes routine.Suggested shape
Ship a
zizmor.ymlfrom this repo as part of the standard workflow set, encoding these two as documented ignores with justifications, so consumers inherit the policy rather than each rediscovering it.Note this repo now needs it too: after #134,
zizmoris clean on both reusable workflows, but one repo-wide High remains —claude.yml:22pinningclaude-assistant.yml@v3by tag. That is this same documented policy, in our own caller stub.Scope note
Encode only the two findings that are genuinely policy.
artipackedis a cautionary example: #123 assumed it was a documented-suppression case, but the code showed nothing actually consumed the persisted credential, so the right fix waspersist-credentials: false(#134) — a real behavior change, not an ignore. A suppression there would have committed a false claim to justify keeping real exposure.Each entry in the config should be justified against the code, not assumed.
twistedmelonman commentedon Aug 17, 2026
Progress: items 1, 2, and 6 resolved
Item 1 —
claude-code-workflows-agentsrejoined the fleet update path. smartwatermelon/claude-code-workflows-agents#6 (mergeddc7e3d4). Replaced the inlinedclaude-code-action@4481e6d3(v1.0.131) job with the standard caller stub at@v3.1.1.Worth recording: the old file was not a stock stub. It carried a ~110-line repo-specific prompt with 9 numbered review criteria. Those were preserved verbatim into the reusable workflow's
extra_instructionsinput rather than discarded. It also dropped a caller-levelif: github.actor != 'dependabot[bot]'gate — the anti-pattern from #115/#117 that leaves a required check permanently pending on Dependabot PRs (v3.1.0+ skips from inside the job instead).Item 2 —
repo-templatestandardized. smartwatermelon/repo-template#5 (merged9ed77b6). Renamed toclaude-blocking-review.yml, pinned@v3.1.1.That work surfaced a bug worse than the drift it was sent to fix:
repo-template'sclaude.ymlhad nopermissions:block at all — the only repo in the fleet in that state. Missingid-token: writemeansstartup_failure, so the template had been shipping a broken assistant workflow into every repo created from it.The old
claude-code-review.ymlfilename is now gone fleet-wide. The 2026-04-02 migration is finally complete.Item 6 — tokens. All repos running the reviewer now have
CLAUDE_CODE_OAUTH_TOKEN. The one exception isrepo-template, which should stay without one: template repos don't propagate secrets to repos created from them, so a token there buys nothing while placing a live credential in a public repo. It's already documented as a manual setup step.New item found:
claude-code-workflows-agents/claude.ymlSame repo, same problem, different file. Its assistant caller still inlines
anthropics/claude-code-action@4481e6d3c7bbb88db2a928ca3444c536f589c7c1(v1.0.131) rather than calling the sharedclaude-assistant.yml. This issue's item 1 only covered the review workflow there. Needs the same migration, pointed atclaude-assistant.yml@v1.2.6.Also shipped
v3.1.2/v1.2.6released and floatingv1/v3repointed tofefbd3d. Consumers now get the template-injection fix,persist-credentials: false, andactions/checkoutv7.0.1 alongside the v1.0.193 advisory fix. Rollback ref:9422220.Still open here
dotfiles(c30bec1, from 2026-04-28) andclaude-wrapper(9615f93). Tag repoints never reach these; they need explicit PRs.@v3, 2 on@v3.0.0, 1 on@v3.1.0.@v1.2.6is current.superpowers,superpowers-marketplace,x-thread-reader..githuborg repo —claude.ymlonly, with unpinned@v1/actions/checkout@v7.zizmor.yml(carried from claude-code-action pinned to v1.0.70 (GHSA-8q5r-mmjf-575q, patched in 1.0.74) — live in ~30 consumer repos #123 item 5) — both migration PRs above independently hit theSKIP=zizmorcollision onunpinned-usesand both flagged it as needing a fleet-level config. That's two more data points for shipping it.docs(readme): deprecate the v1 tag line, document repo-wide tag seman…
twistedmelonman commentedon Aug 17, 2026
Raw-SHA pins fixed — GHSA-8q5r-mmjf-575q is now closed fleet-wide
smartwatermelon/claude-wrapper#112 (
66c3f92) and smartwatermelon/dotfiles#192 (e05472e) both merged, movingclaude-assistant.ymlfrom a raw commit SHA to@v3.1.2.Verified by sweep across all 27 non-fork repos: no vulnerable inline pin and no SHA-pinned first-party ref remains anywhere.
Both were confirmed genuinely exposed before the fix —
c30bec1(2026-04-28) and9615f93(2026-08-07) each still carriedclaude-code-action@26ec0412= v1.0.70. Neither had anyworkflow_callinterface drift, so both were one-line changes.Worth recording: both stale SHAs were annotated
# v3. They looked like they tracked the v3 tag, which is how they escaped earlier audits — including mine. A grep for tag refs classifies them as compliant. I swept for 40-char SHA refs across the fleet and these two were the only instances, but the lesson generalizes: the version comment can lie, so audit the ref itself.New item: document the two-class pinning policy
dotfiles' pre-push reviewer filed smartwatermelon/dotfiles#191 arguing the SHA→tag move was a supply-chain downgrade. Closed as answered, but it revealed a documentation gap worth fixing here.The reviewer inferred a contradiction that doesn't exist, because the two pinning rules are never stated side by side:
README.md:207documents the first-party tag convention, but doesn't contrast it with the third-party SHA rule or explain why they differ. Any future SHA→tag change will trip the same review flag.Today is the argument for the split: the two repos that SHA-pinned our own workflow were the only two the fleet-wide fix couldn't reach. The hardening measure caused the exposure it was meant to prevent, and left them stale for four months.
Suggest folding this into the
zizmor.ymlwork — same theme (encoding deliberate policy so tooling and reviewers stop re-litigating it), and theunpinned-usessuppression needs precisely this justification written down anyway.twistedmelonman commentedon Aug 17, 2026
Correction: GHSA-8q5r-mmjf-575q was NOT closed fleet-wide
I reported the advisory as fully remediated earlier in this issue and in #123. That was wrong. 19 repos are still resolving to the vulnerable
claude-code-action@26ec0412(v1.0.70).The error in my verification
My sweeps checked two things: whether any consumer file contained the vulnerable SHA inline, and whether any consumer pinned the reusable workflow by raw commit SHA. Both came back clean, and I concluded the fleet was patched.
Neither check resolved what each consumer's pinned tag actually serves.
Nearly every repo pins
claude-blocking-review.yml@v3.1.0. That tag is immutable and predates the fix, so it still serves the vulnerable action:Repointing the floating
v3/v1tags remediated only the repos that use floating tags. For the blocking reviewer, that was essentially none of them.Actually exposed (19)
claude-config,claude-wrapper,crazy-larry,dev-env,dotfiles,dumbify,github-workflows,lock-sync,mac-dev-server-setup,mac-server-setup,personify,pr-review,projectinsomnia,qwen-sidebar,scripts,slack-mcp,smartwatermelon-marketplace,spokane-snow,swift-progress-indicatorPlus assistant callers on exact pins:
dumbify(@v3.1.0),personifyandpr-review(@v3.0.0).Genuinely patched:
archive-resolver,homebrew-tap,repo-template,claude-code-workflows-agents(all bumped to@v3.1.1during this work), and the 9 assistant callers already on floating@v3.Root cause: exact pins opt repos out of remediation
This is the structural problem behind the whole issue.
README.mdcurrently advises "new callers should pin@v3.1.0or later", which produces a fleet where a security fix requires a PR in every consumer repo rather than one tag move.The intended model — and the reason the floating tags exist — is that consumers track what
github-workflowspublishes. Exact pins silently defeat that.Fix in progress: moving all reviewer callers (and the exact-pinned assistant callers) to floating
@v3. That both remediates now and prevents recurrence: the next advisory is one tag repoint, not a 20-repo sweep.The
README.mdversioning guidance needs updating to match, or it will keep steering new repos back onto exact pins. Folding that into the pinning-policy documentation work noted above.Lesson for the audit
A conformance check must resolve refs, not pattern-match them. Two failure modes hit this fleet today:
# v3reads as tag-tracking to a grep.@v3.1.0reads as conformant while serving months-old content.Both look correct in the consumer file. Only following the ref to what it actually serves catches either.
docs: recommend floating @V3, fix examples that taught exact pinning (#…
twistedmelonman commentedon Aug 17, 2026
GHSA-8q5r-mmjf-575q verified closed — resolve-based audit, 41 refs, 0 vulnerable
All 19 exposed repos are remediated. Every consumer now tracks floating
@v3, which resolves toclaude-code-action@9d7150bc(v1.0.193).18 PRs merged: claude-config#322, dev-env#56, lock-sync#34, scripts#121, spokane-snow#14, slack-mcp#31, qwen-sidebar#30, crazy-larry#15, mac-dev-server-setup#70, mac-server-setup#170, projectinsomnia#118, smartwatermelon-marketplace#27, swift-progress-indicator#28, dumbify#4, dotfiles#196, claude-wrapper#116, personify#48, pr-review#12.
github-workflowsitself needed no change: its only caller isself-review.ymlusing a local path (./.github/workflows/...), the deliberate dogfooding pattern, which resolves to the branch's own definition and was never exposed.This audit resolves refs rather than matching patterns
My earlier all-clear on this issue was wrong because it grepped consumer files for the vulnerable SHA and for raw-SHA refs. Both came back clean while 19 repos were still resolving to v1.0.70 through an immutable
@v3.1.0.The audit now follows each ref to the action it actually delivers, and is validated against a known-bad control before its result is trusted:
Three things that looked correct and weren't
All three were invisible to pattern-matching, and each one cost a wrong conclusion before it was caught:
# v3— reads as tag-tracking to any grep. (dotfiles,claude-wrapper)@v3.1.0— reads as fully conformant in the consumer file while serving months-old content. (19 repos)run-reviewjob passed in 6-9s via theworkflow-self-modificationskip path, soclaude-code-actionwas never invoked. The green check was not evidence the fix worked.Only resolving refs, and checking step durations rather than conclusions, catches these. Both lessons are now documented in
README.md(#138).The recurrence mechanism is fixed
#138 also corrected the source: the commented caller example at
claude-blocking-review.yml:37and the README setup example both told new callers to pin@v3.1.0, and the versioning table recommended it outright. That guidance is what produced the exposure. Both examples and the table now lead with floating@v3, and exact pins are documented as a deliberate opt-out of automatic security updates.Net effect: the next advisory is one tag repoint, not a 20-repo sweep.
Two local reviewers tried to block the fix
Worth recording, because it will recur. The pre-push codebase reviewer hard-BLOCKED the change in
dev-env,lock-sync,scripts, andsmartwatermelon-marketplace, arguing floating@v3is a supply-chain downgrade. Two claims, both verified wrong:@v3is a security downgrade" — inverted.@v3.1.0is the tag pinning the vulnerable v1.0.70.@v3may lack the v3.1.0+ Dependabot skip" —git compare v3.1.0...v3returns ahead 19, behind 0. Strict superset; the skip logic is present.No hook was bypassed. The reviewer's one legitimate catch was real and got fixed: several repos carried
# Pinned to v3.1.0+comments that the ref change made false. Those were reworded (not reverted) after confirmingv3containsv3.1.0.Tracked upstream as smartwatermelon/dev-env#57 — Pattern 2 of that repo's security methodology needs a first-party carve-out, or it will keep hard-blocking correct changes and training people to bypass the reviewer.
fix(scripts): guard rm -rf, stop bypassing hooks, float the embedded …
twistedmelonman commentedon Aug 17, 2026
Items resolved: assistant migration, empty repos, script hardening
claude-code-workflows-agents/claude.yml— rejoined the fleetsmartwatermelon/claude-code-workflows-agents#10 (merged
89f1dad). The assistant no longer inlinesclaude-code-action@4481e6d3(v1.0.131); it callsclaude-assistant.yml@v3. All 9 CI checks pass.This surfaced a real gap, filed as #141.
claude-assistant.ymlaccepts noworkflow_callinputs — only the secret. Migrating therefore forced dropping that repo's deliberate tool allowlist, which enumerated read-onlygit/ghsubcommands (status,diff,log,show,rev-parse,ls-files— nopush,reset,checkout) so destructive variants stayed unreachable regardless of the action's defaults. The assistant now runs on default tool policy withid-token: writeand the OAuth token.That's a security regression accepted deliberately: being 62 releases stale and outside all remediation was the larger risk. But it shouldn't stand, hence #141 (add
allowed_tools/modelinputs, mirroring whatclaude-blocking-review.ymlalready offers viaextra_instructions).Repos with no workflows — 1 onboarded, 2 deliberately excluded
x-thread-reader— onboarded (smartwatermelon/x-thread-reader#1), both callers on floating@v3, token already present.superpowersandsuperpowers-marketplace— excluded, not gaps. Both reportfork: falseto the API, which is why this issue's audit flagged them. Their commit histories say otherwise:superpowersobra— 522 commitssuperpowers-marketplaceobra— 119 commitsThey're detached mirrors of
obra/*, not repos we develop in. Onboarding them would mean the blocking reviewer fires on large upstream-sync diffs — real spend to review code we didn't write. Neither hasCLAUDE_CODE_OAUTH_TOKEN, so the workflows couldn't run anyway.Recording so future audits don't re-flag these as missing. The right conformance rule is not "every repo with
fork: false" but "every repo we actually develop in" — a distinction the GitHub API cannot make for detached forks.Restore script hardened
#140 (merged
3d6c409).nightowl-restore-blocking-review.shhad three defects worth closing before any reuse — it is dormant, but loops over a dynamically-discovered repo list and deletes paths:rm -rf "${WORK_DIR}/${repo}".set -ucatches unset but not empty variables, and the repo list comes fromgh repo listoutput. An empty$repowould have wiped the whole work dir. A textual "starts with$WORK_DIR/" guard is insufficient —../escapepasses it — so the fix validates the name as a single plain path segment. Verified against 8 inputs.@v3.0.0— older than the@v3.1.0that caused this issue's exposure. It would have deployed that same defect across every NightOwl repo it touched. Now floats@v3.Remaining
zizmor.yml— now the last item here. Three separate agents hit theunpinned-usesblock on the@v3tag ref today and each neededSKIP=zizmor. Every future onboarding hits the same wall./install-github-app:superpowers,superpowers-marketplace— only relevant if they're ever onboarded.feat(zizmor): ship a config so consumers stop bypassing the linter (#143
twistedmelonman commentedon Aug 17, 2026
Final items closed — this issue is complete
zizmor.ymlshipped (#143, merged7100b28). Consumers copy it alongside the caller stubs; it ends theSKIP=zizmortreadmill that three separate agents hit during this work.Verified it is not a blanket mute:
validate.yml— a real gap, deliberately left visible.artipacked.The
"*": hash-pinline is what preserves third-party findings. A blanketunpinned-usesignore — the obvious implementation — would have hidden them.x-thread-readeronboarded (smartwatermelon/x-thread-reader#1, merged831928a). Both callers on floating@v3; it already had the token.superpowersandsuperpowers-marketplacewere deliberately not onboarded — detached mirrors ofobra/*(522 and 119 upstream commits vs 1 and 2 from this account), where the reviewer would fire on upstream-sync diffs at real cost. Reasoning recorded above.Every item in this issue is now resolved
claude-code-workflows-agents)claude-code-review.ymlfilenamedotfiles,claude-wrapper→@v3@v3.githuborg repozizmor.yml(ex-#123 item 5)The
.githuborg repo still has an unpinnedclaude.yml(@v1,actions/checkout@v7). Leaving it: that repo hosts org-level templates andFUNDING.ymlrather than reviewable code, and its workflow-template copy is superseded by the caller stubs in the README, which are now the canonical source. Worth a separate decision about whether that repo needs the standard set at all — not a fleet-conformance gap.What this issue actually cost, for the record
The audit that opened it found 6 items. The work surfaced 4 more that were invisible to the original scan, each because a check pattern-matched instead of resolving:
@v3.1.0pins served months-old content while looking conformant.@v3.1.0(fixed in docs: recommend floating @v3, fix examples that taught exact pinning #138).claude-assistant.ymlaccepting no inputs (claude-assistant.yml accepts no inputs, so callers must inline the action to set claude_args — forcing a choice between fleet conformance and tool restrictions #141).Three things looked correct and weren't: a raw SHA annotated
# v3, an exact tag serving stale content, and green CI on PRs where the reviewer had skipped. All three are now documented inREADME.md, with the resolve-the-ref command.Related issues opened along the way: #133 (a single GitHub 503 fails the reviewer and blocks merges fleet-wide), #141 (assistant inputs), smartwatermelon/dev-env#57 (Pattern 2 first-party carve-out), smartwatermelon/dotfiles#190 (global pre-commit hooks conflict with repo-local config), smartwatermelon/homebrew-tap#16 (Test Casks).
feat(claude-assistant): add allowed_tools and model inputs (#145)