Single source for Driver's Bonsai→GitHub pipeline reusable workflows and the onboarding kit
(templates/github/ in this repo). Each consuming repo installs a thin caller stub per workflow that pins
an immutable commit SHA; a bot (Dependabot/Renovate) bumps the SHAs as new tags ship. This repo is
public so cross-repo reusable calls resolve from any consuming repo (the org enforces a
selected-actions allowlist at the org/enterprise tier).
- workflows (this repo, public) — the reusable GitHub workflows + this onboarding kit. Fleet repos carry thin SHA-pinned caller stubs; these run the GitHub side (the implementer and the Dependabot rails) once an issue exists.
- driver-agents (private) — the Shopify
admin-tool wrapper and the canonical operator instructions, cloned at the pinned
DRIVER_AGENTS_REF; also the pipeline dispatcher (pipeline-dispatch.yml, scheduled) that triages ready Bonsai tasks, opens those GitHub issues, and polls the Bonsai status back. It was also the box's headlessclaude -pcron runner. - driver-bonsai-mcp — archived 2026-09-11; it held the dispatcher and the Bonsai bridge server until 2026-09-10. Mentions of it below are history.
Flow: a Bonsai ticket assigned to Agents → the dispatcher triages it and opens a GitHub issue →
the target repo's claude.yml implements it and opens the PR → the dispatcher moves the Bonsai
task to Internal Review.
Latest tag v1.18.0 (85f1578, 2026-10-01) — dependabot-validate no longer fails every clean
Dependabot PR, the implementer prompt carries the revised comments standard, and the house standards
gain the subagents-and-replies rule; hours after v1.17.0, which made claude.yml a reusable + stub.
Both canaried, then waved the same night — see v1.18.0 below. The four
caller stubs are pinned to 85f1578.
State of play, open decisions and next steps: docs/HANDOFF.md.
Deployed fleet stubs are repinned by the wave, tools/fleet-wave.sh. Dependabot does bump these
pins in a repo that has a github-actions block, but the wave repins within minutes of every tag so
it rarely gets the chance —
docs/fleet-operations.md. Org Actions secrets
(AGENTS_GH_PAT, CLAUDE_CODE_OAUTH_TOKEN, SHOPIFY_ALERT_WEBHOOK) and cross-repo Actions access
are already in place — no per-repo secret setup.
Tags are human labels + the bot's bump target; the caller stubs pin the SHA. History: v1.0.0 (initial
rail) → v1.0.1 (no-ticket detection fix) → v1.0.2 (dependabot-report bot-actor fix) → v1.1.0 (add
dependabot-keep-current + claude-code-action bump) → v1.1.1 (keep-current fail-loud fix) → v1.2.0
(PR-first house-style review) → v1.3.1 (PR-first: REQUIRE the allow-list naming the inline-comment
poster) → v1.4.0 (PR-first outcome-aware marker) → v1.5.0 (add the ticketed-review reviewer-loop
rail) → v1.5.1 (drop the gh-based author re-check that skipped every real PR) → v1.5.2
(allowed_bots: claude[bot], so the bot-opened round 1 actually reviews) → v1.5.3 (always-latest
resilient Claude Code self-install in the three agent reusables) → v1.5.4 (dependabot-validate:
npm-install fallback for lockfile-less repos + actions/checkout v7) → v1.5.5 (claude-code-action
1.0.161 → 1.0.168 in the agent reusables) → v1.6.0 → v1.7.0 → v1.8.0 → v1.9.0 → v1.10.0 →
v1.11.0 → v1.12.0 → v1.13.0 → v1.14.0 → v1.15.0 → v1.16.0 (all below). v1.3.0 was never tagged.
dependabot-validate no longer fails every clean Dependabot PR (#73), the implementer prompt carries
the revised comments standard (#74, closing #70), and the house standards gain the
subagents-and-replies rule (#72). Hours after v1.17.0. Canaried on vite-plugin-shopify-clean
(implementer: issue #107 → PR #108; validator: a @dependabot rebase of #97), then waved to all
22 targets (20 pushed; driver-onboarding joined the targets through a hand-installed
claude-standards.md). Audit the same night: 69 pins at 85f15787, 127 files matching
templates/, 0 drifted.
- The validator bug. Since v1.15.0 (#45) the registry-names pipeline in
dependabot-validate.ymlreturned 1 underbash -e+pipefailwheneverbuild.logcarried nonpm error <pkg>@line — every clean run — and the step died silently beforeresult.jsonwas written. So every clean Dependabot PR carried a redvalidate / validate(a required check on most repos) for three weeks, while PRs with a real peer-dependency failure were reported properly. The fix is one|| true. Found through foundrae-blackridge #206/#207 (#69, whose dev-smoke diagnosis was wrong: that repo has noagent-validate.json, so dev-smoke never ran). - Canary: implementer run green, App token obtained, no validation-skip warning, PR with
Bonsai task: none; validate on the rebased Dependabot PR green withregistry factsandoverall=0, four artifact files, the report job ran;bonsai-linkskipped (grey) on both. - Reached the fleet with this wave, having landed kit-only since v1.17.0: the wave installs
claude-standards.mdbesideclaude.yml(#67),pr-bonsai-link.ymlskips PRs with no Bonsai mention (#68), the revised comments standard and the style-only header (#71). - Dependabot PRs opened before this release still show the old red validate on their current head;
a
@dependabot rebase(or any push) gets a fresh, green run.
claude.yml is a reusable + thin stub (#60; the Phase 0 spike passed 2026-09-30). Canaried on
vite-plugin-shopify-clean, then waved to all 21 targets the same night (20 pushed, the canary
already current; driver-engineering-app joined the targets through its .github/claude-standards.md).
Audit the same night: 69 pins at 359505a7, 110 files matching templates/, 1 drifted —
driver-agents' hand-installed lint.yml (comment-only; the wave never reaches that repo), re-copied by
hand the same night (ed2ad6a).
- The reusable carries every ungated ride-along from
docs/claude-yml-wave-plan.md: the round-marker arm and re-request step are gone, no GitHub reviewer is requested, PR bodies carryBonsai task:and name store changes, the house comment and commit rules, Figma MCP, the blockquote andDRIVER_AGENTS_REFat driver-agents0397630, and a guard that fails a silent validation skip. Beyond the plan: a "Trusted authors" step refuses outsider-authored issues and PRs, and fork PRs, before checkout; the store token is minted in its own step so the log masks it; the full transcript is on for private repos only. - Kit: the
claude.ymlstub (triggers, concurrency, five permissions,secrets: inherit),pr-bonsai-link.ymlinstalled besideclaude.yml, the PR template'sBonsai task:line,shopify-tool-smoke.ymlreading theSHOPIFY_STORE_NAMErepository variable, andclaude-standards.mdgains theponytail:comment rule. The wave installspr-bonsai-link.ymland refuses a store repo until its variable holds the handle, checked fleet-wide before the first push; the audit flags a missingpr-bonsai-link.ymland no longer normalizes the handle. - Canary (vite-plugin-shopify-clean #101 → PR #102, run 36786817542): App token obtained, no
Skipping action due to workflow validation, the Trusted-authors step listed collaborators with the default token, the PR carriedBonsai task: noneand passedbonsai-link; a review event and a plain comment each skipped the job with no runner. Closed unmerged. - Macroscope: approvability stayed "not approved" on the release-order finding (the kit ships the stub only at step 2); answered in-thread and merged past by decision. Its manual pass over the commits after its third automatic run found nothing. The transcript-on-private-repos residual is accepted in its thread.
- Not yet proven: Avara's first store run (
Provisioned store 'avara'and the mint step), one real ticket end to end, andbonsai-linkas a required check per repo —docs/HANDOFF.md.
Every third-party action floats on its major tag, in the kit and the reusables, and Dependabot
proposes majors only. Canaried on vite-plugin-shopify-clean, then waved 2026-09-10 to all 20
targets (19 pushed, the canary already current). Audit the same day: 51 pins at ff3ff344, 88
files matching templates/, 2 drifted — the hand-installed lint.yml copies in driver-agents and
driver-engineering-app, which the wave never reaches (neither carries claude.yml or a stub); a
to-do is filed in each and the rows clear when they land.
- Why. A SHA in
templates/is a pin nothing bumps (Dependabot scans.github/workflows/only), while a fleet repo's Dependabot bumps its deployed copy when the action releases, so the fleet always ran ahead of the kit and each wave rolled it back — foundrae-blackridge #174 tookclaude-code-action1.0.195 → 1.0.210 and the v1.15.0 wave put it back to 1.0.201. Maria's policy (2026-09-10): action refs stay current with no commit anywhere, and a breaking change is fixed after the fact rather than every patch shepherded across 20 branches. - Refs:
actions/checkout@v7,actions/upload-artifact@v7,actions/download-artifact@v8andanthropics/claude-code-action@v1— inclaude.yml,lint.ymlandshopify-tool-smoke.yml, the three Dependabot reusables, and this repo's ownlint.yml. A run resolves the tag fresh, so every minor and patch arrives on its own; Dependabot opens a PR only for a new major, and Anthropic re-pointsv1within seconds of each release. - Dependabot, here and in the kit: minor and patch bumps of
actions/*andanthropics/*are ignored (a floating ref already has them, and it closes a reported edge where Dependabot rewrites a major-tag ref into a pinned patch). The kit block names the two owners rather than*, so theDriverDigital/workflowsstub bumps it exists for still flow — the tagged kit file said*; corrected onmainthe same day, before any repo installed it. Here,github-ownedmajors still auto-merge andclaude-code-actionmajors land by hand. - The trade, recorded in
claude.yml:v1is a mutable tag a bot repoints, and the SHA covered one of two channels into a job holding the OAuth token — the CLI install was already unpinned — so a small reduction, not none. Macroscope's Critical onactions/checkout@v7(#55) was assessed and kept, by decision, in its thread. - Docs updated in place:
CLAUDE.md's invariant, README's "Template pins are manual", the audit's normalization note,fleet-operations.md, the kit README,HANDOFF.md's watch-items, and the conversion scope doc, whose "three action pins" secondary win is gone.
The wave that carries #50's issue-path guard, plus the ride-alongs decided 2026-09-10. Canaried on
vite-plugin-shopify-clean, then waved 2026-09-10 to all 20 targets (19 pushed, the canary
already current). Audit the same day: 51 pins at 15a34e98, 90 files matching templates/, zero
drift — the first audit that counts the PR template.
- An issue run can no longer end green without a PR (#50): a guard step fails the job when no
open PR exists from
issue-<n>(Avara #195 pushed its branch and stopped, and nothing noticed).show_full_output: truekeeps the transcript in the masked job log — the log, not an artifact, so registered secrets stay redacted (Macroscope on #50).CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1is set at the step env and through thesettingsenv (claude-code-action#1499: a run that backgrounds subagents exits green with the work orphaned; the step-env form is what three independent reports measured, the settings path is what the CLI reads from disk). claude-code-action→c81e3bc(v1.0.201) in the kit, matching the reusables since #47. foundrae-blackridge@staging had been moved to 1.0.210 by its own Dependabot (#174), not by hand; the wave brought it back to the kit's pin, and when Dependabot bumps it again the audit will read that as the kit being behind.DRIVER_AGENTS_REF→b4ea94e(driver-agentsmain, 2026-09-10) in both kit files. driver-agents PR #7: the guard reports a value-clearing write (valueof"",[]or{}) on the allow path — a stdout line, not a gate — and the canonical blockquote names three exit-3 refusal kinds and adds two paragraphs (an empty value is a delete; theaccess.adminenum). Kit copy re-copied, 2,370 → 3,480 chars, parity verified whitespace-collapsed at the pin.templates/github/dependabot.ymlgains acooldownblock: GitHub's default 3 days stays for third-party actions;DriverDigital/workflows*is exempt, so a repo the wave skipped bumps the day the tag lands.- The wave replaces every kit file whole-file, the three stubs included (the pin-line sed let
per-repo stub edits survive, but the audit reports those as drift and none existed), and
pull_request_template.mdjoins the file set at.github/— the fleet's copies still credited the retired status sync, invisible to the audit until it learned the path. Presence-based as before: replaced where present, never installed. tools/fleet-pin-audit.shselects the newestvX.Y.Zby semver across every tag page and fails closed on a failed page or an empty list (#51 — three Macroscope findings, each fixed).dependabot-reportheaders (reusable and stub) no longer claim a reusable cannot seegithub.event.workflow_run; the explicit inputs exist for the provenance assertion (#51).
The queued DRIVER_AGENTS_REF bump and tripwire re-copy, plus the kit's first Dependabot updater.
Waved 2026-08-22 to 19 of the 20 repin targets — vite-plugin-shopify-clean deliberately
skipped (--skip) to prove Dependabot bumps the stub pins unaided. Audit the same day: 48 pins
at 539d7eab, 68 files matching templates/, and exactly the expected drift — 3 stale pins and 4
content rows, all vite-plugin-shopify-clean@main. fleet-pin-audit.sh --stale reads red by design
until that repo catches up.
DRIVER_AGENTS_REF→919ca79(driver-agentsmain, committed 2026-08-07) in both kit files. Undertools/the diff is one stderr message, jq summary phrases and a comment — no file, mode, env var, argument or exit-code change.- Tripwire blockquote re-copied from canonical, 978 → 2,370 chars: the two exit-3 refusals are
split (allowlist vs argument guard),
menuUpdate/ metaobjectvalues/collectionUpdateruleSetare flagged as replace-not-patch,redirectNewHandleis required on a handle change, and the false "extend the allowlist with one reviewed line" blanket is gone. Parity verified whitespace-collapsed at the pin. templates/github/dependabot.yml— a daily groupedgithub-actionsblock, hand-installed (it lives at.github/and merges into a repo's existing file; the three install cases are in the kit README). The fleet's existing blocks are monthly and a Dependabot PR waits on a human merge, so the wave stays primary —docs/fleet-operations.md.tools/fleet-wave.sh --skip <repo>, repeatable, resolved like--onlyso case or a typo cannot wave the repo it was meant to spare; both flags refuse an empty value. The default commit message no longer names the v1.13.0 retirement.claude.ymlride-alongs: the danglingdocs/phase2-github-setup.mdpointer is gone and a Figma MCP caveat sits beside the WebSearch one above--allowedTools(docs/figma-mcp-in-ci.md).- this repo's own CI: the kit actionlint pass excludes
dependabot.yml(not a workflow).CLAUDE.mdadded (#41).
The pass that sets the rail up to run without the box — see the headline for what and why. Piloted
on vite-plugin-shopify-clean (canary below), then waved 2026-08-22 to all 20 repin targets (19
pushed, the pilot already current). Audit clean the same day: 51 pins at f6d25d34, 72 files
matching templates/, zero drift across the 23 pairs audited.
claude.ymlreads the dispatcher's issue body.Target branch:is the base for bothgh issue develop --baseandgh pr create --base;Reviewer:is requested withgh pr edit --add-reviewerafter the PR exists, non-fatally — a bad handle can no longer abort PR creation;## Instructions from the ticketdirects the task but never overrides the steps, the repo rules or a system-prompt guard, and never authorises touching.github/or exposing credentials. Branch name and handle are opaque data: one quoted argument each, never spliced into a larger command.- In-run pre-review. Push, then
/code-review high <base>...HEAD— the explicit range is load-bearing, a bare invocation reviews commits ahead of upstream, which is nothing once the branch is pushed. Fix, push again, and the short PR body endsPre-review: N findings, M fixed, K dismissed(orPre-review: skipped (reason)). - Quality standard added to the static
--append-system-prompt(9 items: CLAUDE.md/HANDOFF first, design before code, one author per file, adversarial pre-review, verify before done, conventional commits with no trailers, short PR body, repo conventions win, judgment over compliance). It is global — it reaches the ticketed revision rail too, so watch the first revision round against the 90-minute cap. - Attribution off mechanically:
settings: { "attribution": { "commit": "", "pr": "", "sessionUrl": false } }— noCo-Authored-By, no "Generated with" line on the agent rails. A human@claude(tag mode) still gets the action's own co-author text. --model fable --effort xhigh, andclaude-code-action→d40ddef(v1.0.195), matching the reusables.bonsai-status-syncretired — template and reusable both deleted, and the org secretBONSAI_BEARER_TOKENdeleted after a direct probe of every non-archived repo (+ Palmersmain*) found no copy of the workflow left anywhere. The dispatcher polls the two remaining legs instead (issue opened → In Progress; non-draft dev-linked PR → Internal Review). The kit is six.ymlfiles; five reusables remain — three Dependabot rails plus the two retired review rails;ticketed-review.yml's banner now says re-activation needsBONSAI_BEARER_TOKENrecreated.tools/fleet-wave.sh— the wave is a checked-in script now. It discovers targets by presence (claude.ymlor a Dependabot stub, which is what reaches the two stub-only pairs) and repins the pin line's SHA and its# vX.Y.Ztrailer together. Guards: a real wave only from a cleanmaincontaining the tag, kit stubs must pin the latest tag, never waves this repo, actionlint before every write, no destination path twice, store handle survives,--dry-runtouches nothing, zero targets is an error. Mechanics:docs/fleet-operations.md.- this repo's own CI:
lint.ymlnow also fails on a stray brace inclaude.yml'sprompt:scalar — actionlint is blind to it, and a stray{is a silent dispatch failure fleet-wide. DRIVER_AGENTS_REFheld at4d63371, re-checked against canonical at that pin (whitespace-collapsed parity: match). driver-agentsmainis 8 commits ahead with a much longer blockquote and small tool fixes; the queued re-copy + ref bump is indocs/HANDOFF.md.- Design: driver-bonsai-mcp
docs/superpowers/specs/2026-08-21-box-retirement-dispatcher-design.md§5a.
Canary (vite-plugin-shopify-clean, 2026-08-21 23:12–23:17 UTC, torn down afterwards): Bonsai
TSK-00923 → dispatcher run 32536010404 → issue #91 by driver-digital-agents carrying the target
branch, reviewer and ticket instructions → claude.yml run 32536083273, success in 3m24s on
claude-fable-5 → PR #92 with ktdriverdigital requested 4 s after creation, a 10-line body ending
Pre-review: 1 finding, 1 fixed, 0 dismissed, no footer, 0 trailers on 2 commits, and the
ticket's changelog instruction honoured. Timestamps prove the order: feat commit 23:15:12 →
pre-review fix 23:16:41 → PR 23:16:49. Dispatcher reconcile run 32536361182 moved Bonsai to
Internal Review.
The review-rail retirement: PR review became Macroscope's job alone (decided 2026-08-08) — the two
review stubs left the kit, their reusables stayed here caller-less with retirement banners, and
bonsai-status-sync lost its review leg (formal review → Revisions Requested / Ready for QA); the
Dependabot rails, claude.yml and the two remaining status legs were untouched. Context and the
Macroscope→Bonsai build plan:
docs/macroscope-integration-scope.md. Waved 2026-08-08 to
all 23 pairs: the two review stubs deleted from every fleet pair that carried them (41 pin rows
removed), bonsai-status-sync.yml whole-file replaced on 18 pairs, remaining stubs repinned.
Audit clean same day: 69 pins at b1fcb78c, 90 files matching templates/, zero drift. Three
pairs' only pinned stub was pr-first-review.yml — driver-agents@main, driver-engineering-app@main,
driver-bonsai-mcp@main — so they now carry no pins at all (still content-checked) and the
repin-target count lands at 20 (see docs/fleet-operations.md).
- Review rails retired, not removed. Stubs deleted (kit + fleet); reusables preserved inert
with retirement banners carrying the re-activation path (restore stubs from git history, tag,
wave). Deletion over
gh workflow disablebecause disabled-state is invisible to the pin audit — the fleet's files should say what actually runs. bonsai-status-syncreview leg removed — the stub'spull_request_reviewtrigger AND the reusable's handler, deliberately not actor-gated: Macroscope's bot behavior (formal reviews vs. comments) is unobserved as of this tag, and if it submits formal reviews the old mapping would flip statuses with the wrong semantics (a bot approval is not "Ready for QA"). Statuses past Internal Review are a PM's job until the Macroscope→Bonsai integration lands.claude.ymldeliberately untouched, including the ticketed-loop machinery (round-marker prompt branch, actor carve-out, re-request step). It looks dead withticketed-reviewgone; it is the designed re-entry point for the Macroscope-driven revise loop — do not strip it in a future wave. (Superseded 2026-09-30: the driver-agents loop summons with a tag-mode@claude; the arm and step go in the nextclaude.ymlwave —docs/claude-yml-wave-plan.md.)DRIVER_AGENTS_REFdid not move. The queued canonical-blockquote re-copy stays pending for the nextclaude.yml-touching release — deliberately not folded in here, so this wave changes review behavior and nothing else.
Waved to all 21 pairs on 2026-08-02; fleet uniform, 108 pins, zero stale.
bonsai-status-sync.ymlconversion completed. The 190-line per-repo copy became a 67-line caller stub — the status machine, actor gate, linkage logic and cascade caveat now live in one central file. The reusable itself landed one tag earlier (seev1.10.0), so the conversion spans the two tags: a new reusable's stub cannot be pinned until the tag containing it exists.- kit
claude.yml+shopify-tool-smoke.yml:DRIVER_AGENTS_REF→4d63371. The previous pin0bbb125predatedgraphql_guard.py, so every fleet runner executedadmin-graphql.shwith no fail-closed allowlist and the Driver Engineering scope grant was the only control on destructive mutations. - Shopify operator tripwire appended to
claude.yml's static--append-system-prompt, pairing with that wrapper. The blockquote is copied verbatim from driver-agentsdocs/agent-instructions-shopify.md(canonical — edit there first); a non-canonical kit-side lead-in precedes it, un-scoping the block from the conduct rules above and telling the model how to report a trip on a rail that cannot set a job exit code. - this repo's own CI:
lint.ymlgained a tokenization guard assertingclaude_argsholds exactly four single quotes and the system prompt contains no apostrophe or$. One apostrophe typed into canonical upstream silently truncates the prompt —shell-quotedoes not throw, every flag still parses, and the wave would copy the truncated prompt fleet-wide green. - Piloted before the wave:
vars.BONSAI_URLproven to resolve against the caller, so the per-repo tunnel override survives the conversion. Seedocs/fleet-operations.md.
- New sixth reusable:
.github/workflows/bonsai-status-sync.yml. Itsjobs:body is byte-identical to the old per-repo copy except one added comment. Deliberately shipped without its caller stub — seev1.11.0. lint.ymlplaceholder-pin guard: fails the build on any kit stub still carrying an all-zero pin, so a stub that cannot resolve can never reachmain.- Never waved on its own; superseded by
v1.11.0two commits later.
- Store app secrets renamed
DRIVER_AGENTS_SCOPES_CLIENT_ID/_SECRET→DRIVER_ENGINEERING_APP_CLIENT_ID/_SECRET, tied to the per-org "Driver Engineering" app that replaced "Driver Agents Scopes" (retired 2026-08-01). Waved to all 21 pairs; Avara's smoke test green on the new names, old-name secrets deleted. - Reusables unchanged. Note this release never got its kit repin commit —
templates/sat atv1.8.0's SHA while the deployed fleet was waved toa54c91e, leaving the fleet a release ahead of the kit templates untilv1.11.0closed it. That gap was invisible totools/fleet-pin-audit.shat the time; its reference check now catches exactly this shape — seedocs/fleet-operations.md.
- kit
claude.yml+shopify-tool-smoke.yml:DRIVER_AGENTS_REF→0bbb125(the audit pipeline:SHOPIFY_AUDIT_CONTEXTon the tool, plus the box's nightlyaudit-publish.sh— design at driver-agentsdocs/audit-data-model.md). Lockstep as always. - Audit context export: the provisioning step now builds
SHOPIFY_AUDIT_CONTEXT(ticket/issue/run/host) so every tool call's audit line says on whose behalf it ran. The Bonsai task uuid is resolved from the ISSUE body (uuid=param — the same linkagebonsai-status-sync.ymlgreps), with aclosingIssuesReferencesfallback for the @claude-on-a-PR rails; a human's ad-hoc@claudehas no ticket, correctly. - Audit artifact upload: a final
if: always()actions/upload-artifactstep (pinnedv7.0.1) ships the runner's throwaway audit log to the box's nightly collector —github.run_attemptin the artifact name dodges the immutable-artifact collision on re-runs. Skips cleanly on repos without store tooling. Same step on the smoke test. - reusables: unchanged; stubs repinned to the new tag SHA for the pin-audit invariant.
- kit
claude.yml+shopify-tool-smoke.yml:DRIVER_AGENTS_REF→0404c4e(driver-agents main @ 2026-07-31) — picks up the human-readable Slack alert wording (driver-agents PR #2). The pin moves in both files together, per the lockstep rule. - CI alerting leg: the same provisioning step now writes the org-level
SHOPIFY_ALERT_WEBHOOKsecret (the#driver-agents-statusincoming webhook) to the runner's throwaway disk and exportsSHOPIFY_ALERT_WEBHOOK_FILE+SHOPIFY_ALERT_HOST_LABEL, so the admin tool's destructive/failed- call alerts post from Actions runs exactly as they did from the box. The alert's "where to look" label is the run URL — the runner's audit log doesn't outlive the job. Absent secret = alerts silently off, nothing else changes (the tool's own best-effort posture). - reusables: unchanged (byte-identical to
v1.6.0's). The stubs are repinned to3966041regardless, purely sofleet-pin-audit.sh's latest-tag comparison stays meaningful.
- reusables:
actions/checkout→v7.0.1,claude-code-action→v1.0.183; each Claude Code self-install attempt is now bounded bytimeout— a stalled download used to hang one attempt until the job's wall-clock cap while the retry loop never advanced. - kit
claude.yml: human@claudecomments always get tag mode (never prompt-hijacked — foundrae-blackridge PR #148); the ticketed round-marker branch is author-gated ondriver-digital-agents+ id261291955; optional self-skipping Shopify admin tool provisioning — pinned to a revieweddriver-agentsrevision, verified before any credential is written, and gated off the read-only/code-reviewrail (Avara PR #161); the same bounded self-install;actions/checkout→v7.0.1andclaude-code-action→v1.0.183. - new kit file
shopify-tool-smoke.yml(store repos only): a manual diagnostic for the Shopify admin-tool wiring, upstreamed from Avara PR #161 so it is maintained here rather than reinvented per repo. It duplicatesclaude.yml's provisioning step by design — same wiring, loud failures instead of degrade — so the two must be kept in lockstep. - this repo's own CI: new
lint.ymlruns actionlint — plus shellcheck over everyrun:block — across the reusables and the kit, so a broken workflow can no longer reach consumer repos.
Release + repin order (don't skip a step — a wave is only safe once all three are done):
- Merge to
main, then cut the new tag.- If the release moves
DRIVER_AGENTS_REF, re-run the tripwire parity check first: extract the>lines from driver-agentsdocs/agent-instructions-shopify.mdat the new pin, strip the>prefixes, NFC-normalize, collapse whitespace, and diff against the blockquote portion of.github/workflows/claude.yml's--append-system-prompt. The whitespace collapse is mandatory — the copy flattens canonical's paragraph break to a single space (forced by the no-newline constraint), so a strict byte compare reports a false failure. Nothing else re-checks this.lint.ymlproves the reusable andshopify-tool-smoke.ymlcarry the sameDRIVER_AGENTS_REF, not that the blockquote matches canonical at it, and the ref is a raw SHA in an env var that no bot can bump.
- If the release moves
- Repin every caller stub in
templates/github/to that tag's SHA and its# vX.Y.Ztrailer, then commit. Until this lands, the kit's stubs still point at the PREVIOUS tag's reusables.- If the release ADDS a reusable, its stub lands in this step, not in the PR that added the
reusable — the tag it must pin does not exist until step 1. That is why
dependabot-keep-current's reusable and its stub landed in different commits, and howbonsai-status-sync.yml's stub landed atv1.11.0.lint.ymlfails the build on any stub left carrying a placeholder pin, so this step cannot be silently skipped.
- If the release ADDS a reusable, its stub lands in this step, not in the PR that added the
reusable — the tag it must pin does not exist until step 1. That is why
- Only then re-copy
templates/github/into consumer repos (tools/fleet-pin-audit.sh --staleto confirm the fleet converged afterwards — it now checks waved file content againsttemplates/, not just the pin line, and exits non-zero on any drift, so a wave can gate on it).- The wave is now a checked-in script:
tools/fleet-wave.sh --dry-runfirst, then without. - Dependabot also bumps the stub pins in any repo with a
github-actionsblock (the kit now ships one,templates/github/dependabot.yml, for the repos that had none) — on its schedule and through a PR a human merges, so the wave stays the primary path and Dependabot the backstop.--skip <repo>leaves every branch of a repo to it on purpose — only sound where Dependabot covers each kit branch (it scans the default branch unless atarget-branchentry exists, so not Palmers as configured). Seedocs/fleet-operations.md. - When a full workflow becomes a stub (as
bonsai-status-sync.ymldid — this applies to the v1.11.0 wave specifically), the wave diff contains atemplates/github/path AND a.github/workflows/path with the SAME basename. The wave script rewritestemplates/github/→.github/workflows/, so assert the rewritten diff touches no destination path twice before applying — otherwise the reusable can land in a client repo as the workflow, where it isworkflow_call-only, fires on nothing, and looks green. - Sed pin lines; never
git applythem. A pin hunk patches from whatever SHA the kit held, which is not necessarily what the fleet holds — at v1.11.0 the kit diff patched from80c35fe(v1.8.0) while every deployed stub helda54c91e(v1.9.0), a SHA no kit revision had ever carried in a pin line, so no diff base produced a matching-line andgit applywould have rejected all five files on target #1. The sed rewrites the SHA and the# vX.Y.Ztrailer in one substitution (asfleet-wave.shdoes) — the two halves are one pin, and a stale comment names the wrong release. - Wave mechanics, the guards worth keeping, and what the pin audit cannot see:
docs/fleet-operations.md.
- The wave is now a checked-in script:
Template pins are manual — so the kit's third-party actions float. .github/dependabot.yml uses
directory: "/", which only scans .github/workflows/ — nothing will ever bump a pin inside
templates/, while a fleet repo running the github-actions updater bumps its deployed copy when the
action itself releases, so a SHA there guaranteed the fleet ran ahead of the kit and each wave rolled
it back (foundrae-blackridge #174). The kit's whole-file workflows and the reusables therefore
reference third-party actions by major tag (actions/checkout@v7, actions/upload-artifact@v7,
anthropics/claude-code-action@v1), which Dependabot leaves alone until a new major exists, so the
only action bump that ever gets a PR — here or in a fleet repo — is a major. DRIVER_AGENTS_REF stays
manual — it appears in the claude.yml reusable and the kit's shopify-tool-smoke.yml, and must carry
the same pin in both (lint.yml checks) or the smoke test verifies a revision the implementer never
runs — and so does the VERSION + SHA256 pair in lint.yml, which must be bumped together or the
checksum check fails the job.
Onboarding a new repo: copy the matching stubs from this repo's templates/github/ into the
repo's .github/workflows/, run a test PR (human + Dependabot), then pin the required check
validate / validate + add a human-approver rule (see First-run / required-check below). Caller stubs
MUST carry their own permissions: block (a repo whose default workflow token is read-only otherwise
produces a silent startup_failure — no check run, no notification).
Reusable (.github/workflows/) |
Privilege | Trigger (in the caller) | Job |
|---|---|---|---|
claude.yml |
secrets (OAuth + PAT + optional store and Figma), write token | issues opened, issue_comment, pull_request_review, pull_request_review_comment |
the implementer: @claude'd issue → dev-linked branch → PR; @claude on a PR → revisions |
dependabot-validate.yml |
none (credential-less) | pull_request |
mechanical install/build/test (+ optional theme/dev-smoke) → upload artifact |
dependabot-report.yml |
secrets (PAT + OAuth) | workflow_run |
reason over the inert artifact → verdict comment + request a human reviewer |
dependabot-keep-current.yml |
PAT only | pull_request (closed) |
rebase out-of-date Dependabot PRs on strict (require-up-to-date) repos; inert elsewhere |
Two review reusables, pr-first-review.yml and ticketed-review.yml, were retired at v1.12.0
(2026-08-08) and deleted 2026-09-30; any tag through v1.16.0 still holds them. Macroscope reviews
all PRs — docs/macroscope-integration-scope.md.
The onboarding kit lives here: templates/github/ (moved from driver-bonsai-mcp 2026-07-15). It
carries a caller stub for each reusable above, plus shopify-tool-smoke.yml (store repos only),
lint.yml (actionlint over the installing repo's own workflows), pr-bonsai-link.yml (fails a PR
that names no Bonsai task; installed beside claude.yml), pull_request_template.md (waved since v1.15.0), claude-standards.md (the house commit and comment
standard, installed beside claude.yml and waved at .github/; each repo's CLAUDE.md imports it) and dependabot.yml (the github-actions updater that bumps
the stub pins between waves — installed by hand, merged into an existing file).
Not every repo takes the whole kit. A repo that is not on the Bonsai → PR pipeline can install
lint.yml alone and skip the rest as inert weight.
driver-agents and
driver-engineering-app run that subset (they took
pr-first-review.yml + lint.yml on 2026-08-02; the review stub was deleted in the v1.12.0
retirement wave). The trade-off is written up in templates/github/README.md under Partial install.
A kit-only addition does not need a tag or a wave. lint.yml shipped without either, and that was
correct: it changes no reusable, repins no stub, and carries no uses: DriverDigital/workflows@<sha> of
its own, so nothing deployed had to move. Note the direction of the obligation — cutting the tag is what
creates the wave, because the moment the latest tag moves the audit's reference check fires
against every stub in templates/ and they must be repinned and re-copied everywhere. Let a kit-only file
ride along with the next release that actually changes a reusable.
claude.yml is a reusable (2026-09-30), so an implementer change reaches the fleet as a repin, not a
whole-file wave. The store handle is the SHOPIFY_STORE_NAME repository variable, so no kit file carries a
per-repo value. Design and spike: docs/reusable-conversion-scope.md.
Two files in .github/workflows/ are this repo's own CI, not products — they are workflow_call-free
and never ship to the fleet: lint.yml (actionlint + shellcheck over the reusables and the kit, so a
broken workflow can't reach consumer repos) and dependabot-auto-merge.yml (auto-merges this repo's own
github-owned Dependabot bumps; the claude-code-action group is deliberately excluded, so those land by
hand). Both are quiet below a major: every third-party action here and in the kit floats on its major
tag, so a run already has each minor and patch, and .github/dependabot.yml ignores everything but majors.
actionlint is a required status check on main (set 2026-08-02) — before that, lint.yml could
report red without being able to block. Note the name collision: this repo's own lint.yml and the kit's
templates/github/lint.yml are different files. The kit one runs actionlint over the installing repo's
.github/workflows/ and nothing else; this one additionally lints templates/github/, gates on placeholder
pins, asserts claude.yml's system prompt still tokenizes, and checks DRIVER_AGENTS_REF matches in the
reusable and shopify-tool-smoke.yml. Both use the job id actionlint, so the
required-check context string is the same either way. enforce_admins stays false here, deliberately
— which means an admin can still merge past a red actionlint. Requiring the check makes it binding for
everyone else and puts a red X in front of an admin who previously had nothing to override; that was worth
having on its own. Flipping the flag would break this repo's own release habit — six commits on main,
v1.9.0's included, were pushed directly with no PR. Detail in
docs/fleet-operations.md.
claude[bot]— the implementer (Phase 2claude.yml), distinct from any reviewer.driver-digital-agents(theAGENTS_GH_PATfine-grained PAT) — the agent-rail actor:GH_TOKENon every agent-actingghstep (the defaultGITHUB_TOKENis used only where a post must not cascade —claude.yml's failed-run notice),dependabot-report's comment/reviewer-request identity, and the author of the dispatcher's issues and revise-loop@claudecomments. It was the reviewer on the retired review rails.- Anthropic billing —
CLAUDE_CODE_OAUTH_TOKEN(Max). Never setanthropic_api_key(it overrides OAuth and bills at API rates).
All three secrets must be org-level Actions secrets available to each consuming repo.
dependabot-validate runs untrusted Dependabot code (install + PR-modifiable build) but holds no
secrets — three independent layers: Dependabot forces a read-only token + no Actions secrets; the caller
stub passes no secrets:; this reusable declares no workflow_call.secrets. dependabot-report holds the
secrets but never checks out PR head and never runs PR code — its agent reads ONLY the inert artifact
(build.log/result.json, never interpolated into a run: line) with a read/write-file-only tool surface
(no gh), after a provenance assertion (event pull_request, actor dependabot[bot], non-empty PR
number, same-repo head). Never use pull_request_target.
Install the matching stubs from this repo's templates/github/ into a repo's .github/workflows/.
Pin every uses: of this repo's reusables to an immutable commit SHA (decided 2026-06-17); a bot (Renovate/Dependabot) bumps the
SHAs. The dependabot-validate stub's name: MUST stay byte-identical (Dependabot validate) across all
repos — the dependabot-report stub's workflow_run trigger name-matches it exactly, and a drift silently
disables the human-ping.
{
"install": "npm ci",
"build": "npm run build",
"test": "npm test",
"themeCheck": "npx @shopify/cli theme check --fail-level error",
"dev": "npm run dev",
"devTimeoutSeconds": 90,
"devReadyRegex": "compiled|ready|built in|Local:"
}All keys optional. Defaults: package manager from the lockfile (npm is the house default), build/test
run only if those package.json scripts exist, themeCheck/dev run only if configured.
Retired with the review rails at v1.12.0 — nothing here reassigns a Bonsai task or requests a reviewer
on review completion any more; the status moves are a PM's job until the Macroscope→Bonsai
integration lands. The implementer requests no GitHub reviewer: since 2026-09-11 the dispatcher
(driver-agents) assigns the reviewer in Bonsai when the PR reaches Internal Review, and that assignment is
the review request.
The bridge server that carried /tasks/reviewer-handoff is retired too; what replaces it for that
build — the Bonsai public API, and the reviewer read off the issue body instead of the Reviewer
field — is in docs/macroscope-integration-scope.md.
dependabot-report still requests a human reviewer on Dependabot PRs (default mcarter-astronautdev,
per-repo override via the PR_REVIEWER_HANDLE Actions variable).
The dependabot-validate job always runs and branches internally (non-Dependabot PRs no-op green), and
must never be if:-skipped. The mechanism is worth stating precisely, because the intuitive version is
wrong: GitHub does accept a check run whose conclusion is skipped. The problem is that if:-skipping
the caller job means the reusable never starts, so the nested validate / validate context is never
created at all — and a required context with no check run for the head commit blocks forever. Reason
about whether a check run exists for the head SHA, not about the word "skipped".
After the first run on a test PR: pin the exact required-check context GitHub reports — for a
reusable-workflow job it is <caller-job-id> / <reusable-job-id>, expected validate / validate (the
workflow display name is NOT part of the context; copy the literal string from the first run's checks list).
Require a human approver (e.g. CODEOWNERS) so no bot signal satisfies the merge gate.