Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
55cf40e
docs: update consumer agent topology
codex-automation Aug 16, 2026
d6b1d62
test: satisfy literal guide quality gate
codex-automation Aug 16, 2026
b0ebe8f
docs: align consumer recovery guidance
codex-automation Aug 16, 2026
1d11327
fix: harden consumer guarded merge
codex-automation Aug 17, 2026
02d13e1
fix: wake guarded merge after review window
codex-automation Aug 17, 2026
7a4a824
fix: close guarded merge review races
codex-automation Aug 17, 2026
8d8de85
fix: recheck merge opt-in at final gate
codex-automation Aug 17, 2026
09cd794
fix: recheck final merge blocker labels
codex-automation Aug 17, 2026
8c931bb
fix: retry transient mergeability state
codex-automation Aug 17, 2026
e7a4a7b
fix: bind guarded merges to default branch
codex-automation Aug 17, 2026
0c36c55
fix: close final guarded-merge races
codex-automation Aug 17, 2026
3591a5b
fix: persist exact-head review windows
codex-automation Aug 17, 2026
07c774b
fix: cover every exact-head transition
codex-automation Aug 17, 2026
47d6afd
fix: isolate synchronized merge guards
codex-automation Aug 17, 2026
cb94337
fix: close final guarded-merge races
codex-automation Aug 17, 2026
317939f
fix: anchor review windows to persisted head events
codex-automation Aug 17, 2026
f3c93b6
fix: harden guarded merge wakeups
codex-automation Aug 17, 2026
110bd4e
fix: recheck head transitions before merge
codex-automation Aug 17, 2026
94f7b57
fix: order final guarded-merge snapshots
codex-automation Aug 17, 2026
7a724b2
fix: trust base workflow for privileged PR wakeups
codex-automation Aug 17, 2026
3f2a432
fix: harden guarded merge followups
codex-automation Aug 17, 2026
45f5987
chore: sync consumer templates
github-actions[bot] Aug 17, 2026
bbc1c6b
fix: refresh final merge metadata
codex-automation Aug 17, 2026
f37023f
fix: bind final merge target snapshots
codex-automation Aug 17, 2026
e3f87d5
fix: reapply blockers at final merge boundary
codex-automation Aug 17, 2026
cbb1bee
fix: recheck linked tasks before merge
codex-automation Aug 17, 2026
a5efa64
fix: use canonical issue reference parser
codex-automation Aug 17, 2026
9c93b69
fix: prefer explicit PR body issue links
codex-automation Aug 17, 2026
f2e3bdb
fix: bind blockers after linked issue read
codex-automation Aug 17, 2026
4df9b25
fix: bind linked issue at merge boundary
codex-automation Aug 17, 2026
ef1cecc
style: format merge guard regression
codex-automation Aug 17, 2026
37dd529
fix: bind final reviews and issue references
codex-automation Aug 17, 2026
72e6869
chore(autofix): formatting/lint
github-actions[bot] Aug 17, 2026
68dc059
fix: bind issue identity before guarded merge
codex-automation Aug 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/scripts/__tests__/keepalive-loop.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const path = require('path');
const {
countCheckboxes,
parseConfig,
resolvePrNumber,
evaluateKeepaliveLoop,
updateKeepaliveLoopSummary,
markAgentRunning,
Expand Down Expand Up @@ -182,6 +183,18 @@ test.after(() => {
}
});

test('resolvePrNumber accepts base-defined pull_request_target events', async () => {
const context = buildContext(123, 9001, { eventName: 'pull_request_target' });

const prNumber = await resolvePrNumber({
github: buildGithubStub(),
context,
core: buildCore(),
});

assert.equal(prNumber, 123);
});

test('countCheckboxes tallies checked and unchecked tasks', () => {
const counts = countCheckboxes('- [ ] one\n- [x] two\n- [X] three\n- [ ] four');
assert.deepEqual(counts, { total: 4, checked: 2, unchecked: 2 });
Expand Down
50 changes: 50 additions & 0 deletions .github/scripts/__tests__/source-context.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,56 @@ test('extractIssueNumberFromPull keeps existing issue resolution behavior', () =
);
});

test('extractIssueNumberFromPull prefers an explicit body link over inferred branch and title issues', () => {
assert.equal(
extractIssueNumberFromPull({
body: 'Related to #222',
head: { ref: 'codex/issue-111-stale-branch' },
title: 'Resolve issue #333',
}),
222,
);
});

test('extractIssueNumberFromPull prefers one closing link over secondary body references', () => {
assert.equal(
extractIssueNumberFromPull({
body: 'Related to #111 for context.\nCloses #222',
head: { ref: 'codex/issue-222-fix' },
title: 'Resolve issue #222',
}),
222,
);
});

test('extractIssueNumberFromPull rejects ambiguous non-closing body references', () => {
assert.equal(
extractIssueNumberFromPull({
body: 'Related to #111.\nReferences issue #222.',
head: { ref: 'codex/issue-333-fallback' },
title: 'Resolve issue #333',
}),
null,
);
});

test('extractIssueNumberFromPull rejects distinct meta issue markers', () => {
assert.equal(
extractIssueNumberFromPull({
body: '<!-- meta:issue:111 -->\n<!-- meta:issue:222 -->',
head: { ref: 'codex/issue-111-fallback' },
title: 'Resolve issue #111',
}),
null,
);
assert.equal(
extractIssueNumberFromPull({
body: '<!-- meta:issue:111 -->\n<!-- meta:issue:111 -->',
}),
111,
);
});

test('extractIssueNumberFromPull ignores PR references in workflow source templates', () => {
const context = resolvePrSourceContext({
body: `
Expand Down
6 changes: 5 additions & 1 deletion .github/scripts/keepalive_loop.js
Original file line number Diff line number Diff line change
Expand Up @@ -1834,7 +1834,10 @@ async function resolvePrNumber({ github, context, core, payload: overridePayload
return overridePayload.workflow_run.pull_requests[0].number;
}

if (eventName === 'pull_request' && payload.pull_request) {
if (
(eventName === 'pull_request' || eventName === 'pull_request_target')
&& payload.pull_request
) {
return payload.pull_request.number;
}

Expand Down Expand Up @@ -5397,6 +5400,7 @@ module.exports = {
parseConfig,
buildTaskAppendix,
extractSourceSection,
resolvePrNumber,
evaluateKeepaliveLoop,
markAgentRunning,
updateKeepaliveLoopSummary,
Expand Down
62 changes: 58 additions & 4 deletions .github/scripts/source_context.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,64 @@ function extractIssueNumberFromText(text) {
return issueNumbers.size > 0 ? Array.from(issueNumbers)[0] : null;
}

function extractClosingIssueNumbersFromText(text) {
const value = String(text || '');
const issueNumbers = new Set();
for (const match of value.matchAll(/#([0-9]+)/g)) {
const before = value.slice(Math.max(0, match.index - 100), match.index);
const token = before.split(/\s/).pop() || '';
if (token.includes('/')) {
continue;
}
const prefix = before
.replace(/\r\n?/g, '\n')
.replace(/[_\[\]()`~]/g, ' ')
.trim()
.replace(/[>*]/g, ' ')
.replace(/\s+/g, ' ');
if (
!/\b(?:close[sd]?|closing|fix(?:e[sd])?|fixing|resolve[sd]?|resolving)(?:\s+(?:source\s+issue|github\s+issue|issue))?\s*[:#-]?\s*$/i.test(
prefix
)
) {
continue;
}
const parsed = Number.parseInt(match[1], 10);
if (!Number.isNaN(parsed)) {
issueNumbers.add(parsed);
}
}
return issueNumbers;
}

function extractIssueNumberFromPull(pull = {}) {
const bodyText = String(pull?.body || '');
const metaMatch = bodyText.match(/<!--\s*meta:issue:([0-9]+)\s*-->/i);
if (metaMatch) {
return Number.parseInt(metaMatch[1], 10);
const metaIssueNumbers = new Set(
Array.from(bodyText.matchAll(/<!--\s*meta:issue:([0-9]+)\s*-->/gi), (match) =>
Number.parseInt(match[1], 10),
),
);
if (metaIssueNumbers.size > 1) {
return null;
}
if (metaIssueNumbers.size === 1) {
return Array.from(metaIssueNumbers)[0];
}

const closingIssueNumbers = extractClosingIssueNumbersFromText(bodyText);
if (closingIssueNumbers.size === 1) {
return Array.from(closingIssueNumbers)[0];
}
if (closingIssueNumbers.size > 1) {
return null;
}

const bodyIssueNumbers = extractIssueNumbersFromText(bodyText);
if (bodyIssueNumbers.size === 1) {
return Array.from(bodyIssueNumbers)[0];
}
if (bodyIssueNumbers.size > 1) {
return null;
}

const branch = String(pull?.head?.ref || '');
Expand All @@ -251,7 +304,7 @@ function extractIssueNumberFromPull(pull = {}) {
return titleNumber;
}

return extractIssueNumberFromText(bodyText);
return null;
}

function parseHtmlMarker(body, name) {
Expand Down Expand Up @@ -489,6 +542,7 @@ module.exports = {
normalizeSourceType,
extractIssueNumberFromText,
extractIssueNumbersFromText,
extractClosingIssueNumbersFromText,
extractIssueNumberFromPull,
parseWorkflowSourceBlock,
parseDependencyRepairPromotionSource,
Expand Down
77 changes: 42 additions & 35 deletions docs/LABELS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ This document describes all labels that trigger automated workflows or affect CI
| `autofix:clean` | PR labeled | Triggers clean-mode autofix (more aggressive)
| `agent:codex` | Issue or PR labeled | Routes the issue or PR to the Codex agent
| `agent:claude` | Issue or PR labeled | Routes the issue or PR to the Claude Code agent
| `agent:cursor` | Issue or PR labeled | Routes the issue or PR to the Cursor agent (`cursor-agent` CLI)
| `agent:gemini` | Issue or PR labeled | Routes the issue or PR to the Gemini agent (`gemini` CLI)
| `agent:cursor` | Issue or PR labeled | Registered routing label; no consumer Gate-followup runner is currently wired
| `agent:gemini` | Issue or PR labeled | Registered routing label; no consumer Gate-followup runner is currently wired
| `agent:aider` | Issue or PR labeled | Routes the issue or PR to the Aider agent for cheap, low-complexity tasks — runner lands in a follow-up phase
| `agent:auto` | Issue or PR labeled | Delegates routing to the auto-delegation policy; do not combine with concrete `agent:<name>` labels
| `agent:retry` | PR labeled | Requests one re-dispatch of the matching keepalive runner
| `agent:retry` | PR labeled | Consolidated consumers require a manual Gate-followups dispatch; the root/non-consolidated keepalive workflow forces a retry and clears recovery labels
| `agent:rate-limited` | Auto-applied | Marks a PR as backing off from a rate-limit failure
| ~~`agent:codex-invite`~~ | *(deprecated)* | No workflow, script, or tool references this label by name; the generic `agent:<name>-invite` mechanism in `reusable-agents-issue-bridge.yml` still works but this specific label is unmaintained — see detail section below
| `agent:needs-attention` | Auto-applied | Indicates agent needs human intervention
Expand Down Expand Up @@ -100,13 +100,13 @@ This document describes all labels that trigger automated workflows or affect CI
2. Validates that a valid agent assignee is present
3. If validated, enables automated code generation for the issue
4. Creates a `codex/issue-<number>` branch for agent work
5. On PRs, routes keepalive dispatch to `reusable-codex-run.yml` per `.github/agents/registry.yml`
5. On PRs, routes keepalive evaluation through the local sweep and the registry-backed shared runner

**Prerequisites:**
- Issue must have a valid agent assignee (configured in repository settings)
- Issue should have clear requirements in the description

**Workflow:** `agents-63-issue-intake.yml` (Agents 63 Issue Intake); on PRs, `agents-keepalive-loop.yml` routes work via `.github/agents/registry.yml`.
**Workflow:** `agents-issue-intake.yml` (Agents Issue Intake); on PRs, `agents-keepalive-sweep.yml` routes evaluation to the shared registry-backed implementation.

---

Expand All @@ -128,7 +128,7 @@ This document describes all labels that trigger automated workflows or affect CI

**Lifecycle:** Applied at issue claim / PR creation by an opener that already has Claude capacity. Removed when work completes (PR merged or issue closed). Co-applied with `agents:keepalive` on the PR so keepalive is enabled.

**Workflow:** `agents-bot-comment-handler.yml`, `agents-auto-label.yml`, `agents-keepalive-loop.yml`, `agents-guard.yml`, `reusable-pr-context.yml`; runner is `reusable-claude-run.yml` per `.github/agents/registry.yml`.
**Workflow:** `agents-auto-label.yml`, `agents-81-gate-followups.yml`, `agents-guard.yml`, `reusable-pr-context.yml`; runner is `reusable-claude-run.yml` per `.github/agents/registry.yml`.

---

Expand All @@ -139,15 +139,15 @@ This document describes all labels that trigger automated workflows or affect CI
**Trigger:** When applied to an issue or PR

**Effect:**
1. Routes the issue or PR to the Cursor agent, a parallel surface to `agent:codex` and `agent:claude`
2. On PRs, keepalive dispatches work via `reusable-cursor-run.yml` per `.github/agents/registry.yml`
3. Branch prefix `cursor/issue-<number>` is used for agent work (see `.github/agents/registry.yml`)
1. Identifies Cursor as the intended route in registry-aware automation
2. Does **not** dispatch a consumer PR keepalive runner: `agents-81-gate-followups.yml` currently has runner jobs only for Codex and Claude
3. Branch prefix `cursor/issue-<number>` is reserved for Cursor work (see `.github/agents/registry.yml`)

**Prerequisites:**
- Repository has a valid `CURSOR_API_KEY` secret (per `.github/agents/registry.yml`)
- Issue or PR should have clear requirements

**Workflow:** `agents-keepalive-loop.yml`, `agents-autofix-loop.yml`; runner is `reusable-cursor-run.yml` per `.github/agents/registry.yml`.
**Workflow:** Registry-aware workflows may recognize the label, but the consumer Gate-followup workflow has no Cursor runner job. Do not use this label to promise PR keepalive execution until that job is delivered from the canonical Workflows source.

---

Expand All @@ -158,14 +158,14 @@ This document describes all labels that trigger automated workflows or affect CI
**Trigger:** When applied to an issue or PR

**Effect:**
1. Routes the issue or PR to the Gemini agent, a parallel surface to `agent:codex`/`agent:claude`/`agent:cursor`
2. On PRs, keepalive dispatches work via `reusable-gemini-run.yml` per `.github/agents/registry.yml`
3. Branch prefix `gemini/issue-<number>` is used for agent work
1. Identifies Gemini as the intended route in registry-aware automation
2. Does **not** dispatch a consumer PR keepalive runner: `agents-81-gate-followups.yml` currently has runner jobs only for Codex and Claude
3. Branch prefix `gemini/issue-<number>` is reserved for Gemini work

**Prerequisites:**
- Repository has a valid `GEMINI_API_KEY` secret (per `.github/agents/registry.yml`)

**Workflow:** `agents-keepalive-loop.yml`; runner is `reusable-gemini-run.yml` per `.github/agents/registry.yml`.
**Workflow:** Registry-aware workflows may recognize the label, but the consumer Gate-followup workflow has no Gemini runner job. Do not use this label to promise PR keepalive execution until that job is delivered from the canonical Workflows source.

---

Expand Down Expand Up @@ -206,20 +206,23 @@ runner and registry entry ship, applying this label will not dispatch a runner.

**Applies to:** Pull Requests

**Trigger:** When applied to (or re-applied to) a PR
**Trigger:** Topology-dependent:

- Consolidated consumer (`agents-81-gate-followups.yml`): the label is a recovery marker and does not trigger a retry by itself
- Root/non-consolidated (`agents-keepalive-loop.yml`): a `pull_request:labeled` event triggers a forced retry

**Effect:**
1. Signals keepalive to force a re-dispatch of the matching runner on its next tick
2. Removed by `agents-keepalive-loop.yml` at the top of the resulting run so the label is reusable
3. Co-removed: any stale `agent:rate-limited` label is also removed at the same time
1. Records that a retry was requested in both topologies
2. Consolidated consumer: does not set `force_retry` or remove recovery labels merely by being applied
3. Root/non-consolidated: sets `force_retry=true` and attempts to remove both `agent:retry` and `agent:rate-limited`

**Prerequisites:**
- PR has a concrete `agent:codex` or `agent:claude` label so a runner can be re-dispatched
- PR has `agents:keepalive`

**Lifecycle:** Applied by `agents-auto-pilot.yml` on dispatch failure handling, by openers/closers during quick recovery, or manually to force one keepalive re-run. Consumed and cleaned by `agents-keepalive-loop.yml`.
**Recovery:** In a consolidated consumer, force a bounded retry by manually dispatching `agents-81-gate-followups.yml` with `pr_number=<PR>` and `force_retry=true`; after the run is confirmed, remove stale recovery labels manually if they remain. In the root/non-consolidated topology, applying `agent:retry` invokes the label handler automatically.

**Workflow:** Applied by `agents-auto-pilot.yml`; consumed/cleaned by `agents-keepalive-loop.yml`.
**Workflow:** `agents-81-gate-followups.yml` accepts the explicit `force_retry` dispatch input for consolidated consumers. `.github/workflows/agents-keepalive-loop.yml` implements label-driven retry and cleanup for the root/non-consolidated topology.

---

Expand All @@ -232,14 +235,14 @@ runner and registry entry ship, applying this label will not dispatch a runner.
**Effect:**
1. Marks the PR as currently backed off due to API/runner rate limits
2. Used with the matching concrete `agent:<name>` label to flag backoff for the current route; switch to `agent:auto` only after removing the concrete routing label
3. Removed by `agents-keepalive-loop.yml` during the `agent:retry` labeled run, before keepalive evaluation
3. Remains an observability/backoff marker until automation or an operator explicitly removes it

**Prerequisites:**
- Applied automatically; no manual action required

**Lifecycle:** Applied by `agents-auto-pilot.yml` when a dispatch hits a rate limit. Cleaned by the retry-label handler in `agents-keepalive-loop.yml` when `agent:retry` is processed. Does not by itself trigger a runner.
**Lifecycle:** Applied when a dispatch hits a rate limit. It does not by itself trigger a runner, and the current consumer Gate-followup wrapper does not promise label cleanup. After a successful forced retry, remove it manually if it remains.

**Workflow:** Applied by `agents-auto-pilot.yml`; consumed/cleaned by `agents-keepalive-loop.yml`.
**Workflow:** Producer workflows apply the marker; recovery uses an explicit `agents-81-gate-followups.yml` dispatch with `force_retry=true`.

---

Expand Down Expand Up @@ -306,7 +309,9 @@ runner and registry entry ship, applying this label will not dispatch a runner.
2. Used in conjunction with `agent:codex` to signal readiness
3. May trigger the next step in the agent automation pipeline

**Workflow:** `agents-70-orchestrator.yml` (Agents 70 Orchestrator)
**Workflow:** `agents-issue-intake.yml` handles ordinary agent intake. The
separate `agents:auto-pilot` route dispatches Agents 71 and the Agents 72
wrapper when end-to-end automation is requested.

---

Expand Down Expand Up @@ -529,7 +534,7 @@ These labels trigger the post-merge verifier workflow on a merged PR.

**To Resume:** Remove the `agents:paused` label.

**Workflow:** `agents-keepalive-loop.yml`
**Workflow:** `agents-81-gate-followups.yml`

---

Expand All @@ -548,7 +553,7 @@ These labels trigger the post-merge verifier workflow on a merged PR.
- PR must have an `agent:*` label
- Gate workflow must pass

**Workflow:** `agents-keepalive-loop.yml`
**Workflow:** `agents-81-gate-followups.yml`

---

Expand Down Expand Up @@ -645,9 +650,8 @@ Prefixed labels such as `verify:runtime-ac` are treated the same as

**Consumers:** `.github/scripts/runtime_ac_merge_guard.js`,
`.github/workflows/agents-73-codex-belt-conveyor.yml`,
`.github/workflows/reusable-70-orchestrator-main.yml`,
`.github/workflows/maint-71-merge-sync-prs.yml`,
`templates/consumer-repo/.github/workflows/agents-81-gate-followups.yml`.
`.github/workflows/agents-81-gate-followups.yml`. Agents 73 is callable-only;
Agents 81 is the active local guarded merge route.

---

Expand All @@ -664,7 +668,8 @@ Prefixed labels such as `verify:runtime-ac` are treated the same as

**Consumers:** `.github/scripts/keepalive_loop.js`,
`.github/scripts/merge_manager.js`,
`.github/workflows/reusable-70-orchestrator-main.yml`.
`.github/workflows/agents-auto-pilot.yml`,
`.github/workflows/agents-81-gate-followups.yml`.

---

Expand All @@ -676,11 +681,13 @@ Prefixed labels such as `verify:runtime-ac` are treated the same as

**Effect:**
1. Records that an issue has active belt work in progress.
2. Is removed by the conveyor after the active work hands off or completes.
2. Is cleared by Agents 81 after it successfully merges the linked completed PR.
3. Is also cleared by Agents 73 when that callable-only recovery path is invoked.

**Consumers:** `.github/workflows/agents-71-codex-belt-dispatcher.yml`,
`.github/workflows/agents-72-codex-belt-worker.yml`,
`.github/workflows/agents-73-codex-belt-conveyor.yml`.
`.github/workflows/agents-81-gate-followups.yml`, and the callable-only
`.github/workflows/agents-73-codex-belt-conveyor.yml` recovery component.

---

Expand Down Expand Up @@ -748,9 +755,9 @@ These labels are used for categorization but do not trigger workflows.
| (none) | `agent:auto` | Delegates routing to `agent_delegation_policy.js`
| `agent:codex` | `agent:auto` | Invalid mixed routing; remove `agent:codex` before using `agent:auto`
| `agent:claude` | `agent:auto` | Invalid mixed routing; remove `agent:claude` before using `agent:auto`
| `agent:<name>` + `agents:keepalive` | `agent:retry` | Forces one re-dispatch; keepalive removes the label at the top of its run
| `agent:retry` | (removed by `agents-keepalive-loop.yml`) | Co-removes any stale `agent:rate-limited`
| `agent:rate-limited` | `agent:retry` | Retry-label handler removes stale `agent:rate-limited` before keepalive evaluation
| `agent:<name>` + `agents:keepalive` | `agent:retry` | Consolidated: records a recovery request; root/non-consolidated: forces the keepalive retry and cleanup handler
| `agent:retry` | Manual Gate-followups dispatch | Consolidated only: forces the retry and does not imply automatic label cleanup
| `agent:rate-limited` | Successful forced retry | Remove stale recovery labels manually if automation leaves them behind
| `agent:codex` | `agent:codex-invite` | Selects issue-bridge `invite` mode for `agent:codex` when `force_mode: false`; with the default forced input mode, the requested mode still wins
| `agent:codex` | `status:ready` | Agent begins processing
| `agent:needs-attention` | (removed) | Agent resumes processing
Expand Down
Loading
Loading