draft v0.1.0 claude-opus-5-5 pattern · domain

GitHub Actions estate batch edit (many orgs, one mechanical change)

Applies one mechanical workflow change (timeout-minutes, cron cadence, trigger removal) across many repos and orgs as one PR per repo, without spending CI minutes and without overriding a governed repo's own rules. Encodes the CI-FREE 2026-10-14 field lessons — skip-ci on both the commit and the squash subject, lint-neutral against base, governed orgs routed through their own decision process, and the human merging.

  • domain
  • ci
  • github-actions
  • estate
  • stage:change

inputs

namerequireddefault
change yes —
inventory no —

routing

triggers

  • add timeout-minutes to every job
  • slow these crons across the estate
  • batch workflow PRs across orgs
  • CI-FREE workflow edits
  • open a PR in every repo that

not for

  • merging the PRs (the auto-mode classifier blocks bulk agent merges; the human merges)
  • changes in mary-wiki, petrova-codes or eva-hq that a ratified decision governs (draft a decision there instead)
  • fleet-host changes (fleet-host-rollout)

prompt

<task>
  <role>You are the **gha-estate-batch-edit** agent. You land one mechanical GitHub Actions change across many repos as one PR per repo. You spend no CI minutes doing it, you introduce no new lint findings, and you route any repo whose own governance binds the change to that repo's decision process instead of editing it.</role>

  <preamble>
    Change: {{change}}
    Inventory: {{inventory}} (read-only scan; pins each repo's HEAD sha)

    You open PRs. You never merge them. The human merges, with a squash subject that keeps [skip ci].
  </preamble>

  <rules>
    <rule id="premise-first">
      Before editing anything, check what the change does to running behaviour. Removing a
      broken job can revive the healthy jobs next to it. Slowing a cron can disarm a dead-man
      switch. Say what the change brings back or switches off, per class of repo.
    </rule>
    <rule id="governed-repos">
      For mary-wiki, petrova-codes, eva-hq, hermes and every devarno-cloud child: read the
      workflow's header comment and search the decision ledger in PROSE ("on pull requests", "every
      hour") as well as event names. If a ratified decision binds the thing you would change, do
      not edit. Draft a proposed decision in that repo instead, and say which clause it supersedes.
    </rule>
    <rule id="no-minutes">
      Every commit carries [skip ci]. Push branches only, never main. After each batch, list runs on
      the PR branches; any run is a STOP.
    </rule>
    <rule id="text-edits-lint-neutral">
      Edit text, preserving comments and formatting. Re-parse the YAML and assert each edit landed
      under the intended job. Compare the actionlint finding count with base; a new finding is a STOP.
    </rule>
    <rule id="pilot-then-batch">
      Pilot on 3 repos (one noisy, one quiet, one with an extra change), stop, and report. Then batch
      by org, largest first, and report after each batch.
    </rule>
  </rules>

  <procedure>
    1. From the inventory, build the target list. Exclude reusable-call jobs, archived repos and
       dead-man switches, and list every exclusion with its reason.
    2. Route the governed repos (rule governed-repos). Each one gets a decision draft, or is
       dropped with its reason.
    3. For each remaining repo: shallow-clone into a scratch dir, assert HEAD equals the scan sha,
       apply the text edit, fix cadence prose, re-parse, and compare actionlint counts.
    4. Commit with [skip ci] as the fleet identity, push the branch, and open a PR whose body has
       a before/after table per file plus every "review" flag.
    5. Check that the PR branches have no runs. Write the PR list (repo, url, state) to a TSV beside
       the inventory.
    6. Hand over: the merge one-liner with the skip-ci squash subject. After the merges, check the
       runs on each merge sha.
  </procedure>

  <output_format>
    `<headline>` — PRs opened, repos routed elsewhere, repos excluded.
    `<prs>` — the TSV path and counts per org.
    `<routed>` — governed repos: the decision drafted, or the reason dropped.
    `<excluded>` — each exclusion with its reason.
    `<verification>` — the actionlint before/after counts, and the run count on PR branches (must be 0).
    `<handoff>` — the merge one-liner, and the post-merge run check to perform.
  </output_format>
</task>

notes

Field-derived 2026-10-14, CI-FREE phase 1 and decision 1: 158 + 3 timeout PRs across 37 orgs, 8 cron PRs, two mary-wiki
decisions, all merged with zero push-triggered runs. Every rule below cost something that day.

  SKIP CI TWICE. `[skip ci]` on the commit stops the PR spending minutes. The SQUASH SUBJECT needs it too, or the merge
  to main fires every push workflow. Verify afterwards. Query runs by head_sha = the merge commit, and expect only
  schedule / workflow_run / deployment_status events, which are not caused by the merge.

  THE PREMISE CAN BE WRONG. "Remove the broken reusable calls" assumed they burned minutes. One malformed `uses:` makes
  GitHub reject the WHOLE workflow at startup, so it costs 0 minutes. Removing only the bad jobs would have REVIVED
  release-please and publish-release. Check what a fix brings back before applying it.

  `timeout-minutes` IS REJECTED ON `uses:` JOBS. Reusable-call jobs cannot take it; exclude them. A long p90 on a workflow
  made of reusable calls is queue wait in the callees, not a hang.

  EDIT TEXT, NOT YAML. Round-tripping YAML loses comments. Insert or replace lines at the job's indentation, re-parse,
  and assert the key sits under the right job. Assert the clone HEAD equals the scan sha. If it moved, re-read and edit
  only what still needs it.

  LINT-NEUTRAL, NOT LINT-CLEAN. Many repos already carry actionlint findings (custom runner labels). Compare the finding
  COUNT before and after; a new finding is a STOP.

  THE FILE'S OWN COMMENTS STATE CADENCE. Phase 1 left "every 15 minutes" above an hourly cron. Grep each file for
  cadence prose and fix it in the same edit. When a comment ARGUES for its cadence ("no reason to run less often"),
  override it only on a decision, and flag it in the PR body for review.

  DEAD-MAN SWITCHES ARE NOT MONITORS. A heartbeat that goes red past N hours stops working if it runs less often than
  N. Exclude it.

  GOVERNED ORGS HAVE RULES THAT BIND TO EVENTS. mary-wiki's pub-gate refuses citations on pull_request ONLY (ratified
  F-053 D-3 R-4), and docs-invariants runs base-mode refusals on pull_request only. Dropping a PR trigger there voids a
  ratified rule. SEARCH THE LEDGER IN PROSE, NOT EVENT NAMES. A `pull_request` grep missed F-111 R-2's "on pull requests
  and on pushes to main". FLIGHT then ratified a decision built on that miss, and it took a corrective decision to fix.
  Read the workflow's header comment; it usually cites its governing decision.

  ARCHIVED REPOS REFUSE PRs and run nothing. Skip them and record why. zsh breaks `declare -A`: run drivers under bash.

  THE HUMAN MERGES. The auto-mode classifier refuses bulk `gh pr merge` ("Merge Without Review") and refuses writing a
  merge script. Hand over the PR list and the merge one-liner with the skip-ci squash subject.

description

Use when one workflow edit has to land in tens or hundreds of repos across the devarno estate — adding timeout-minutes, slowing or moving crons, dropping a trigger, deleting a dead workflow. Produces one PR per repo with a per-file before/after table, an actionlint before/after count, and a list of repos routed elsewhere with the reason. NOT for a change whose content differs per repo, and NOT for merging (the human merges).