fix: feature-ideation reusable honours AGENTS_PAUSED, so a fleet pause stops it

Merged
DO
10d ago
+479-0

Problem

AGENTS_PAUSED=true is the switch that pauses the agent fleet. feature-ideation-reusable.yml calls the model (the Claude action, in job analyze) and never reads the variable, so a fleet pause does not stop it. Its callers are frozen stubs, so the gate has to live in the reusable.

Design

  • Both working jobs carry if: vars.AGENTS_PAUSED != 'true'. analyze is the model call. gather-signals calls no model, but its only product is the artifact analyze reads; left running during a pause it would start a runner, upload an orphan artifact, and could turn a paused run red on an unrelated API error.
  • A new fleet-paused job runs on the complementary condition and prints one notice naming AGENTS_PAUSE_SOURCE and AGENTS_PAUSE_UNTIL. It has no needs:, no secret, no token, no action and permissions: {}.
  • The two values reach the script through env: and have line breaks flattened to spaces, so a multi-line value cannot start a second workflow command. (The notice jobs in petry-projects/.github-private do not flatten; filed there as petry-projects/.github-private#2282.)
  • Matching rule, the same as petry-projects/.github-private: only the word true pauses, in any letter case, because GitHub compares strings without regard to case. Unset, false, 1, yes and a padded value run the workflow as before.
  • vars is read in the calling repository's context, so an organisation variable pauses every consumer.
  • No caller stub, channel tag or CI workflow is touched.

Scope item 3 of the issue (other model calls in this repository). Of the 14 workflow_call reusables, this is the only one that calls a model. One non-reusable workflow also does and is not gated: compliance-audit-and-improvement.yml, job analyze. Filed as #1309; not changed here.

Which channel tags have to move (acceptance criterion 3). Consumers get the gate only when the tag they pin reaches a commit that contains this change:

TagNowPinned by
feature-ideation/v1-next4bb3e7aa (v1.20.0).github-private
feature-ideation/v1-ring02e3d523b (v1.16.1)this repository's own stub
feature-ideation/v1-ring1cd0b1675 (v1.11.0)TalkTerm, bmad-bgreat-suite
feature-ideation/v1-stablecd0b1675 (v1.11.0)every other consumer, and the template stub

Most consumers are on v1-stable, which is nine releases behind next, so the pause does not reach them until the last promotion.

Risk

  • A paused run ends green and the canary gate counts it as a healthy sample. The stub's prep job always succeeds, so a paused run ends success with or without the notice job, and ring1 -> stable needs only one sample. Filed as #1310. It becomes reachable when this gate reaches a channel; it should be fixed before a pause is relied on.
  • The tests are static. Only a live run can prove that GitHub resolves vars.AGENTS_PAUSED from the caller inside this workflow_call (the jobs-if context allows vars, and dev-lead-reusable.yml in .github-private relies on the same thing across repositories), that the organisation variable's visibility covers the consumers, and that the paused run shows the one notice.
  • "feat: Switch feature-ideation-reusable.yml model default to the opus family" (#1245) edits the same file at lines 53 to 56. The hunks here are elsewhere; git merge-tree against its head is clean and the new suite passes on the merged tree.

Test plan

  • test/workflows/feature-ideation/fleet-pause.bats (new, 12 tests), picked up by the existing feature-ideation-tests.yml path filters. It parses the committed workflow and evaluates each job's if: with a small model of GitHub's expression rules for eight values of the variable; the model refuses any expression form it does not know, so a rewritten gate fails until the test is updated. It also executes the notice script.
  • Mutation-checked, 15 mutations, each turns named tests red: gate removed from analyze, condition inverted, notice job dropped, a variable interpolated into run:, an || bypass after the gate, a misspelt variable name, a second ungated model job, the notice job given a credential, and others. I re-ran the first independently after rebasing (6 of 12 red, then 12 of 12 green restored).
  • actionlint with CI's flags: clean. Other suites unchanged before and after.
  • Not runnable locally: 14 existing tests in this directory need jsonschema or yq. CI is the authority.

Rollback

Revert the squash commit. Channels that already carry the change keep it until they are re-cut or rolled back; the gate is inert while the variable is unset.

Monitoring

  • AGENTS_PAUSED is unset today, so the first runs after each promotion should look exactly as before: fleet-paused skipped, the two working jobs running.
  • The first run during a real pause should show gather-signals and analyze skipped and one "Agent fleet paused" notice. That run is the live proof the tests cannot give; record it on the issue.

Closes #1295

šŸ¤– Generated with Claude Code