GitHub Actions integration¶
steer ships a few kinds of GitHub Actions integration. Two are installed by
default - claude.yml (keeps CI Claude consistent with local sessions) and
Dependabot (keeps dependencies patched, and manages the resulting PRs). Three
more are opt-in: steer-loop.yml, the scheduled autonomous loop /steer:loop
scaffolds on demand; a gh aw recipe for unattended automation; and
copilot-setup-steps.yml, which preinstalls the toolchain for GitHub's cloud
coding agent (see Copilot support).
claude.yml - the @claude mention workflow (default)¶
/steer:setup init and /steer:setup adopt install .github/workflows/claude.yml (source:
plugins/steer/templates/github/workflows/claude.yml). It runs
anthropics/claude-code-action@v1
when someone mentions @claude on an issue or PR.
Why it loads the steer plugin¶
steer's whole premise is "org standards in every session" - injected by a SessionStart hook the moment the plugin loads. Without extra wiring, the in-CI agent would run as a stock, standards-less Claude: no stack defaults, no Definition of Done, no spec/drift discipline. The shipped workflow closes that gap by loading the plugin in CI through the action's purpose-built inputs:
Once the plugin installs, its SessionStart hook injects rules/* exactly as it
does locally - the in-CI agent and the local agent are governed by the same
rules, with no duplicated system prompt.
settings.json does not work in CI
Do not try to load steer in CI via a .claude/settings.json
enabledPlugins / extraKnownMarketplaces block (the mechanism used for
local sessions). Those are gated behind an interactive trust dialog that
headless/print mode skips, so they load nothing and fail silently
(anthropics/claude-code #13096).
The action's plugins / plugin_marketplaces inputs are the only CI path.
Credentials¶
| Credential | Kind | Required | Purpose |
|---|---|---|---|
ANTHROPIC_API_KEY |
secret | always | Anthropic API auth for the action. A 401 in the log means it is missing, wrong, or mis-scoped. |
No marketplace credential is required: element22llc/e22-plugins is a public
repo, so the plugin_marketplaces fetch clones it anonymously over HTTPS -
ANTHROPIC_API_KEY is the only secret claude.yml needs. (While the marketplace
was private, a shared read-only GitHub App - STEER_APP_ID /
STEER_APP_PRIVATE_KEY - minted a short-lived clone token. That App and those
org variables/secrets are no longer needed and the workflow no longer references
them; org owners can retire them at their convenience.)
Verify by mentioning @claude and confirming the reply reflects steer standards
(e.g. it cites the Definition of Done) - that proves the plugin loaded, not just
that the action ran. The workflow log's system/init event also lists loaded
plugins. See the scaffold README.md -> "GitHub Actions secrets" for the
product-repo-facing version of this.
Dependabot - dependency updates + scoped auto-merge (default)¶
/steer:setup init and /steer:setup adopt install two files (sources under
plugins/steer/templates/github/):
.github/dependabot.yml- thegithub-actionsecosystem is enabled live (every scaffolded repo ships workflows); thenpm/pip/docker/terraformblocks are commented out for init/adopt to uncomment per detected stack (terraformis the one aninfrarepo needs) (mirroring howci.ymlgates stack steps). Updates are grouped, and major bumps areignored - they're deferred to a deliberatepolicy/versions.ymldecision..github/workflows/dependabot-auto-merge.yml- auto-approves Dependabot patch/minor PRs, waits for the requiredcicheck, then merges that single PR. Major bumps are never auto-merged; they get a "left for a human" comment.
The review-gate exception¶
steer normally requires a human-approved PR before anything lands on main. The
auto-merge workflow is a deliberate, documented exception: dependency bumps
don't touch application logic, so the human review is waived. It is not a
waiver of the tests - the workflow waits for the required ci check to go green
before it merges, so a bump that breaks tests, lint, or the version-pin scan never
lands. CI, not a human, is what guarantees the bump is safe. The exception is
declared in policy/branch-protection.yml and the scaffold README.md
branch-protection section.
Auto-merge is scoped to Dependabot - no repo-wide switch
The merge is gated by the workflow's
if: github.event.pull_request.user.login == 'dependabot[bot]' guard and uses a
direct single-PR merge. The guard reads the PR's author, deliberately not
github.actor - that names the last actor to touch the context, so a crafted
HEAD commit can make it report dependabot[bot] while the rest of the branch is
the attacker's. It deliberately does not enable
GitHub's repo-wide allow_auto_merge setting, which would expose an auto-merge
button to every PR. gh pr checks --watch --required watches only required
checks, so the job never deadlocks on its own non-required run.
/steer:setup protect enables the repo settings the exception relies on - Dependabot
alerts and security updates (so security PRs get opened) - alongside secret
scanning. It configures settings only; the merge itself is enacted by the workflow.
/steer:setup sync keeps both files wired (the dependency-automation capability).
Production promotion gate¶
Branch protection covers more than the default branch. policy/branch-protection.yml
also describes a long-lived prod branch, and /steer:setup protect applies
protection to every entry in its protected_branches list, not just main.
This is how the deployment standard enforces its
production gate without GitHub Enterprise. Promotion to production is a reviewed
PR from main into prod; the required-review approval on that PR is the
production approval - it stands in for the deployment-environment approvals that
only GitHub Enterprise provides. Merging the prod PR auto-deploys production, and
nothing is ever pushed to prod directly.
/steer:setup protect reads protected_branches and configures each branch's rules
(required PR, required ci check, no direct pushes); /steer:setup sync keeps the
policy file and the protection in step as the plugin evolves.
Because ci is a required check on prod, the shipped ci.yml must trigger on
PRs targeting prod as well as main - its pull_request.branches is
[main, prod]. A repo whose ci.yml predates that (or was forked from it) sees the
promotion PR wait forever on a check that never reports; add prod to the trigger to
clear it. See
Deployment & environments for the full
promotion model.
steer-loop.yml - the scheduled autonomous loop (on demand)¶
/steer:loop instantiates .github/workflows/steer-loop.yml from
plugins/steer/templates/github/workflows/steer-loop.yml. It is not
bootstrapped by /steer:setup init or /steer:setup adopt and is deliberately absent from
MANIFEST.md's install map - a repo runs a loop only when someone asks for one
(rule 53-autonomous-loops). The first thing scaffold asks for is the
automation opt-in where policy/automation.yml does not already declare
loops: true: an in-session decision with the trade stated, taken before
anything is written.
It is steer's own unattended path: the template ships a schedule: trigger
defaulting to weekday mornings (cron: "0 13 * * 1-5", 13:00 UTC), which
/steer:loop confirms or adjusts with the dev at scaffold time, plus a
workflow_dispatch so the loop can be tested before its first scheduled run.
Keep the cadence modest - an hourly loop burns API budget and opens draft-PR
noise. The verify mode reports an installed loop; remove takes it back out.
Agentic workflows (gh aw) - optional, opt-in¶
GitHub Agentic Workflows (gh aw) is a
GitHub Next tool for authoring CI automation as natural-language Markdown that
compiles to a standard Actions .lock.yml. It can run unattended - on
repository events or a schedule, with no @claude mention - which claude.yml
cannot do, since it only reacts to @claude. (steer's own unattended path is
steer-loop.yml, above; gh-aw differs in authoring CI automation as
natural-language Markdown rather than a fixed loop.)
steer ships one example workflow,
plugins/steer/templates/github/agentic/triage.md (unattended issue triage that
runs when an issue is opened/reopened and classifies it against the steer label
taxonomy and Issue Types). It is not installed by /steer:setup init or
/steer:setup adopt and is deliberately absent from MANIFEST.md's install map
(the manifest names the directory only to say it does not ship) - you opt in
deliberately.
That label taxonomy is bootstrapped by the local /steer:work issues lifecycle, which
runs gh label create --force inline for repo-level label setup - the one
sanctioned exception to routing all tracker I/O through the issue-scoped
/steer:tracker-sync gateway, which has no op for repo-level label creation.
Why it is not in the default scaffold¶
- gh-aw is a self-described research demonstrator - "not a product, not even a technical preview." Committing it into every product repo would couple steer's deterministic, SHA-pinned, human-gated posture to a preview tool.
- It overlaps with steer's own issue lifecycle.
/steer:work issues triage(via/steer:tracker-sync) already triages interactively. Run one of the two, not both, or you double-triage every issue. - It introduces a second agent engine and a manual compile step (
.lock.yml) that the team owns and must keep current (gh aw update).
The recipe¶
- Install the CLI:
gh extension install github/gh-aw. - Copy
plugins/steer/templates/github/agentic/triage.mdinto your repo's.github/workflows/and adapt it. - Compile:
gh aw compile triage.md-> producestriage.lock.yml. - Review the generated lock file before trusting it - confirm the only
write-backs are the declared
safe-outputs(the example is advisory-only: it relabels, sets the Issue Type, and comments, but never closes issues or resolves product/technical questions - those stay human-gated) and confirm every action it references is SHA-pinned (gh-aw pins by default; keep it). - Set
ANTHROPIC_API_KEY(the example uses the Claude engine, consistent with local sessions) and commit both files.
See the authorization model for why the human gate on merge/close/deploy is preserved here.