Skip to content

Codex harness: add working-set rebuild circuit breaker to stop runaway context loops - #55562

Open
pelikhan with Copilot wants to merge 6 commits into
mainfrom
copilot/deep-report-add-context-rebuild-circuit-breaker
Open

Codex harness: add working-set rebuild circuit breaker to stop runaway context loops#55562
pelikhan with Copilot wants to merge 6 commits into
mainfrom
copilot/deep-report-add-context-rebuild-circuit-breaker

Conversation

Copilot AI commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Codex-engine runs were intermittently failing with driver_exit, including a costly failure mode where context rebuilding spirals and burns large token volume before crash. This change adds a guardrail in the codex harness to terminate runaway rebuild behavior early and classify it as infrastructure-incomplete instead of retrying into further spend.

  • Circuit breaker for context-rebuild runaway

    • Introduced a codex runtime guard that inspects live working-set metrics from token-usage.jsonl.
    • Trips only when both thresholds are exceeded:
      • rebuild_factor >= max_rebuild_factor
      • cumulative_input_tokens >= min_cumulative_input_tokens
    • On trip, terminates the child process, emits report_incomplete, and stops retrying.
  • Configurable policy with safe defaults

    • Added env-driven controls:
      • GH_AW_CODEX_CONTEXT_REBUILD_CIRCUIT_BREAKER (on/off)
      • GH_AW_CODEX_MAX_REBUILD_FACTOR (default: 25)
      • GH_AW_CODEX_REBUILD_MIN_CUMULATIVE_INPUT_TOKENS (default: 1000000)
      • GH_AW_CODEX_REBUILD_GUARD_POLL_MS
      • GH_AW_CODEX_REBUILD_GUARD_TERM_GRACE_MS
    • Behavior defaults to enabled, tuned for defense-in-depth.
  • Runtime guard plumbing in process runner

    • Extended runProcess(...) with a generic runtimeGuard callback/poll loop.
    • Added explicit result fields (runtimeGuardFired, runtimeGuardReason) so harness retry logic can classify guard-triggered exits deterministically.
  • Token-usage input correctness

    • Updated working-set read path to consume the first valid token-usage source, avoiding duplicate aggregation across multiple candidate files.
const decision = evaluateContextRebuildCircuitBreaker(workingSet, {
  maxRebuildFactor: 25,
  minCumulativeInputTokens: 1_000_000,
});
if (decision.terminate) {
  emitInfrastructureIncomplete(decision.reason, { logger: log });
  return { action: "stop" }; // no further retries
}

gh-aw-pr-sous-chef
Run: https://github.com/github/gh-aw/actions/runs/32800975910

Generated by 👨‍🍳 PR Sous Chef · pi · gpt54 · 20.9 AIC · ⌖ 8.06 AIC · ⊞ 8.7K ·
Comment /souschef to run again

Copilot AI and others added 2 commits August 25, 2026 01:08
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Add context-rebuild circuit breaker for Codex engine Codex harness: add working-set rebuild circuit breaker to stop runaway context loops Aug 25, 2026
Copilot AI requested a review from pelikhan August 25, 2026 01:12
@github-actions

Copy link
Copy Markdown
Contributor

🎯 Great work addressing the codex engine reliability crisis! This circuit breaker implementation is exactly the kind of defense-in-depth mitigation needed for the 49.4% failure rate issue (#55550).

What's solid here:

  • Clear, focused scope — adds the circuit breaker without scope creep
  • Comprehensive test coverage — new tests in both codex_harness.test.cjs and process_runner.test.cjs
  • Configurable and safe — defaults are sensible (25x rebuild factor, 1M tokens), but environment variables let ops tune as needed
  • Strong description — explains the problem, the solution, and includes a usage example
  • Proper process — initiated via issue discussion ([deep-report] Codex engine driver_exit collapse (49.4% success) — add context-rebuild circuit breaker #55550) with agentic development workflow

This looks ready for review by the maintainers. The code is well-structured, the tests provide good coverage, and the solution is pragmatic — it won't fix the underlying crash but will prevent the catastrophic token burn (worst case was 5M+ tokens before crash) on future incidents.

Generated by ✅ Contribution Check · copilot · auto · 46.5 AIC · ⌖ 6.86 AIC · ⊞ 9.3K ·

@pelikhan
pelikhan marked this pull request as ready for review August 25, 2026 01:58
Copilot AI balanced review requested due to automatic review settings August 25, 2026 01:58
@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR does not have the 'implementation' label and has ≤100 new lines of code in business logic directories (0 additions in src/, lib/, pkg/, internal/, app/, core/, domain/, services/, api/).

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

🔎 Code quality review by PR Code Quality Reviewer

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Ponytail Reviewer completed successfully!

Generated by Ponytail Reviewer for #55562

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

Test Quality Sentinel skipped because pre-fetch PR data was unavailable: unable to fetch test file diff

🧪 Test quality analysis by Test Quality Sentinel

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a Codex context-rebuild circuit breaker to limit runaway token consumption and classify terminated runs as infrastructure-incomplete.

Changes:

  • Adds configurable working-set thresholds and token-usage monitoring.
  • Extends the process runner with runtime guards.
  • Adds circuit-breaker and termination tests.
Show a summary per file
File Description
actions/setup/js/codex_harness.cjs Implements and integrates the circuit breaker.
actions/setup/js/codex_harness.test.cjs Tests configuration and threshold evaluation.
actions/setup/js/process_runner.cjs Adds runtime-guard polling and termination.
actions/setup/js/process_runner.test.cjs Tests guard-triggered termination.
actions/setup/js/harness_retry_runner.cjs Extends attempt-result metadata.

Review details

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 5/5 changed files
  • Comments generated: 3
  • Review effort level: Balanced

Comment thread actions/setup/js/codex_harness.cjs Outdated
if (!stat || stat.size <= 0) continue;
const content = fs.readFileSync(candidate, "utf8");
if (!content.trim()) continue;
return calculateWorkingSetFromJSONL(content).workingSet;
}
: undefined,
});
return { ...result, safeOutputsByteOffset };
Comment thread actions/setup/js/codex_harness.cjs Outdated
return {
enabled,
maxRebuildFactor: Number.isFinite(maxRebuildFactorRaw) && maxRebuildFactorRaw > 1 ? maxRebuildFactorRaw : DEFAULT_CONTEXT_REBUILD_FACTOR_LIMIT,
minCumulativeInputTokens: Number.isFinite(minCumulativeInputTokensRaw) && minCumulativeInputTokensRaw > 0 ? Math.floor(minCumulativeInputTokensRaw) : DEFAULT_CONTEXT_REBUILD_MIN_CUMULATIVE_INPUT_TOKENS,
@github-actions

Copy link
Copy Markdown
Contributor

Comment Memory

reviewed_at: 2026-08-25T00:00:00Z
review_event: REQUEST_CHANGES
top_themes:
  - token-usage path ordering can read stale metrics
  - circuit-breaker classification correctness
files_reviewed:
  - actions/setup/js/codex_harness.cjs
  - actions/setup/js/codex_harness.test.cjs
  - actions/setup/js/harness_retry_runner.cjs
  - actions/setup/js/process_runner.cjs
  - actions/setup/js/process_runner.test.cjs
comment_count: 1

Note

This comment is managed by comment memory.

It stores persistent context for this thread in the code block at the top of this comment.
Edit only the text inside the backtick fences; workflow metadata and the footer are regenerated automatically.

Learn more about comment memory

🔎 Code quality review by PR Code Quality Reviewer · pi · gpt54 · 8.08 AIC · ⌖ 6.95 AIC · ⊞ 7K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes

This guardrail is heading in the right direction, but the token-usage reader is still making a path-ordering assumption that can terminate the wrong run or miss the runaway one entirely.

Blocking theme

The new circuit breaker stops at the first non-empty token-usage.jsonl candidate. If that path contains stale or partial data while a later candidate has the active run's metrics, the harness will classify the run from the wrong dataset. For infrastructure protection logic, that is a correctness bug, not just an observability gap.

🔎 Code quality review by PR Code Quality Reviewer · pi · gpt54 · 8.08 AIC · ⌖ 6.95 AIC · ⊞ 7K
Comment /review to run again

Comment thread actions/setup/js/codex_harness.cjs Outdated
return {
enabled,
maxRebuildFactor: Number.isFinite(maxRebuildFactorRaw) && maxRebuildFactorRaw > 1 ? maxRebuildFactorRaw : DEFAULT_CONTEXT_REBUILD_FACTOR_LIMIT,
minCumulativeInputTokens: Number.isFinite(minCumulativeInputTokensRaw) && minCumulativeInputTokensRaw > 0 ? Math.floor(minCumulativeInputTokensRaw) : DEFAULT_CONTEXT_REBUILD_MIN_CUMULATIVE_INPUT_TOKENS,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The circuit breaker reads the first existing token-usage.jsonl candidate and stops there, but this repo writes the same metrics to multiple path variants during setup/teardown; if the first file is a stale partial copy while a later path has the current run's data, you'll either miss a runaway loop or trip on old tokens from another phase.

💡 Why this blocks merge

readWorkingSetFromTokenUsage() now returns on the first non-empty file instead of reconciling freshness. That makes correctness depend on path ordering, not on which file actually belongs to the active run. In the failure mode this change is supposed to prevent, stale metrics are worse than no metrics: the harness can terminate a healthy run or ignore the real runaway one.

Prefer selecting the newest file by mtime (or validating a run/session identifier in the JSONL if one exists) before computing the working set, e.g.

const candidates = paths
  .filter(p => fs.existsSync(p) && fs.statSync(p).size > 0)
  .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
for (const candidate of candidates) {
  // read newest valid file first
}

That keeps the circuit breaker tied to the current run instead of whichever path happens to be checked first.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. readWorkingSetFromTokenUsage now stats all candidates, sorts them by mtimeMs descending, and reads the newest one first, so selection no longer depends on path ordering. Candidates whose parsed measurement_state is "unavailable" are skipped so a stale/malformed file can't mask a valid one. Covered by the new prefers the most recently written token-usage candidate and skips token-usage candidates whose measurements are unavailable tests in codex_harness.test.cjs.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one-off circuit breaker does not need a generic process-level guard abstraction; folding the stop logic back into the harness would remove indirection and keep the flow easier to follow. net: -18 lines possible.

Generated by ✂️ Ponytail Reviewer for #55562 · codex · mai10 · 8.02 AIC · ⌖ 1.65 AIC · ⊞ 16.7K
Comment /ponytail to run again

* @returns {Promise<{exitCode: number, output: string, hasOutput: boolean, durationMs: number, watchdogFired: boolean, runtimeGuardFired: boolean, runtimeGuardReason: string}>}
*/
function runProcess({ command, args, attempt, log, logArgs, env, postResultWatchdog, stallWarningIntervalMs }) {
function runProcess({ command, args, attempt, log, logArgs, env, postResultWatchdog, runtimeGuard, stallWarningIntervalMs }) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actions/setup/js/process_runner.cjs:103: yagni: generic runtimeGuard hook with poll/term config and extra result fields for a single caller. Inline the termination logic in codex_harness.cjs.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /codebase-design, /tdd, and /diagnosing-bugs — requesting changes on a correctness risk and missing test coverage. Positive: the circuit-breaker abstraction is well-isolated and the new runtimeGuard interface in process_runner.cjs is clean and generic.

📋 Key Themes & Highlights

Key Themes

  • Blocking I/O in event-loop timer (readWorkingSetFromTokenUsage uses sync fs inside setInterval): at the default 15 s poll interval this is low severity, but it is still a code-smell that conflicts with Node.js conventions and could bite if the poll interval is shortened.
  • Shared signal-state between two independent timers: sentSigtermAt/sentSigkillAt are shared between postResultWatchdogTimer and runtimeGuardTimer, creating subtle ordering-dependent behaviour around grace period enforcement.
  • Missing boundary and null tests for evaluateContextRebuildCircuitBreaker: exact-threshold and null/NaN input paths are untested.
  • Off-by-one in config validation: maxRebuildFactor > 1 silently ignores the value 1.

Positive Highlights

  • ✅ Clean separation: evaluateContextRebuildCircuitBreaker is a pure function — easy to unit-test and reason about.
  • ✅ Env-variable parsing is defensive (NaN / non-finite checks throughout).
  • runtimeGuard interface in process_runner is generic and reusable — good deep-module design.
  • runtimeGuardFired / runtimeGuardReason result fields make retry-loop classification deterministic.
  • ✅ Good test for the end-to-end guard-requested termination path in process_runner.test.cjs.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · copilot · sonnet46 · 45 AIC · ⌖ 10.5 AIC · ⊞ 7.6K
Comment /matt to run again

}

/**
* @param {ReturnType<typeof calculateWorkingSetFromJSONL>["workingSet"] | null} workingSet

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] readWorkingSetFromTokenUsage uses synchronous fs.existsSync / fs.statSync / fs.readFileSync inside a setInterval callback that fires every 15 s by default. These blocking calls stall the Node.js event loop on each tick, which can delay the postResultWatchdog and log draining.

💡 Suggestion: use async fs

Switch readWorkingSetFromTokenUsage to async and make shouldTerminate return a Promise:

async function readWorkingSetFromTokenUsage(paths = TOKEN_USAGE_PATHS) {
  for (const candidate of paths) {
    try {
      const content = await fs.promises.readFile(candidate, "utf8");
      if (!content.trim()) continue;
      return calculateWorkingSetFromJSONL(content).workingSet;
    } catch {
      continue;
    }
  }
  return null;
}

The runtime-guard poll loop in process_runner.cjs would then await the result before acting, keeping the event loop free.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. readWorkingSetFromTokenUsage is now async and uses fs.promises.stat / fs.promises.readFile; shouldTerminate returns a promise and the runtime-guard poll loop in process_runner.cjs awaits it, with an in-flight flag so slow reads can't overlap across ticks. New test: supports an asynchronous shouldTerminate without overlapping polls in process_runner.test.cjs.

log(`attempt ${attempt + 1}: post-result watchdog forcing process exit after ${watchdogTermGraceMs}ms grace (SIGKILL)`);
const source = runtimeGuardFired ? "runtime guard" : "post-result watchdog";
log(`attempt ${attempt + 1}: ${source} forcing process exit after ${termGraceMs}ms grace (SIGKILL)`);
child.kill("SIGKILL");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] The runtimeGuardTimer shares sentSigtermAt / sentSigkillAt with postResultWatchdogTimer, so if the watchdog has already sent SIGTERM the runtime-guard timer's SIGKILL branch (line ~237) uses runtimeGuardTermGraceMs instead of watchdogTermGraceMs. This is subtle: whichever timer fires second changes the grace period in-flight, potentially cutting short the watchdog's intended grace window.

💡 Suggestion

Track separate sentSigtermAt timestamps for each timer (e.g. watchdogSentSigtermAt / guardSentSigtermAt) so each enforces its own grace period independently. This also removes the implicit coupling between the two signal sources.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. The shared sentSigtermAt / sentSigkillAt are now split into watchdogSentSigtermAt / watchdogSentSigkillAt and guardSentSigtermAt / guardSentSigkillAt, so each source enforces its own grace period independently. The runtime guard also escalates via a dedicated setTimeout(termGraceMs) instead of waiting for the next poll tick, and watchdogFired is now derived solely from watchdogSentSigtermAt.

evaluateContextRebuildCircuitBreaker(
{
measurement_state: "measured",
rebuild_factor: 4.5,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The test for evaluateContextRebuildCircuitBreaker only checks the "both exceeded → trip" and "token count below → no trip" cases. Missing coverage:

  • rebuild_factor exactly at threshold (boundary: factor === maxRebuildFactor should trip, factor < maxRebuildFactor should not)
  • workingSet is null or missing fields (guard should return terminate: false)
  • Non-finite / NaN values for rebuild_factor
💡 Suggested additional cases
it("does not trip when rebuild_factor is exactly below threshold", () => {
  const cfg = { maxRebuildFactor: 4, minCumulativeInputTokens: 1000 };
  expect(evaluateContextRebuildCircuitBreaker({ rebuild_factor: 3.99, cumulative_input_tokens: 2000 }, cfg).terminate).toBe(false);
});

it("trips when rebuild_factor equals threshold", () => {
  const cfg = { maxRebuildFactor: 4, minCumulativeInputTokens: 1000 };
  expect(evaluateContextRebuildCircuitBreaker({ rebuild_factor: 4, cumulative_input_tokens: 2000 }, cfg).terminate).toBe(true);
});

it("does not trip when workingSet is null", () => {
  expect(evaluateContextRebuildCircuitBreaker(null, { maxRebuildFactor: 4, minCumulativeInputTokens: 1000 }).terminate).toBe(false);
});

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the suggested cases: does not trip when rebuild_factor is just below the threshold, trips when rebuild_factor is exactly at the threshold, and does not trip for null, empty, or non-finite working sets (covering null, {}, NaN/Infinity rebuild factors, and a NaN cumulative token count).

const enabled = enabledValue == null || !/^(0|false|off|no)$/i.test(String(enabledValue).trim());
const maxRebuildFactorRaw = Number(env.GH_AW_CODEX_MAX_REBUILD_FACTOR);
const minCumulativeInputTokensRaw = Number(env.GH_AW_CODEX_REBUILD_MIN_CUMULATIVE_INPUT_TOKENS);
const pollIntervalRaw = Number(env.GH_AW_CODEX_REBUILD_GUARD_POLL_MS);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnosing-bugs] maxRebuildFactor > 1 is the validity check, but 1 itself is rejected and falls back to the default (25). A value of exactly 1 is arguably a valid and very aggressive threshold. Consider using >= 1 or documenting the intended lower bound explicitly.

💡 Context
maxRebuildFactor: Number.isFinite(maxRebuildFactorRaw) && maxRebuildFactorRaw > 1
  ? maxRebuildFactorRaw
  : DEFAULT_CONTEXT_REBUILD_FACTOR_LIMIT,

If an operator sets GH_AW_CODEX_MAX_REBUILD_FACTOR=1 intending "trip immediately when any rebuild occurs", they'll silently get 25. A comment or >= 1 would remove ambiguity.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to >= 1 with a comment explaining that a factor of exactly 1 means "no rebuild at all" and is therefore the most aggressive valid threshold, while anything below 1 is unreachable. Covered by the accepts a rebuild factor threshold of exactly 1 test.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circuit breaker review — harden + audit pass

The working-set rebuild circuit breaker is a solid reliability improvement. The core logic is well-structured with clean separation between config resolution, working-set reading, and evaluation. Tests cover the main happy/unhappy paths.

Three issues found, one reliability-impacting:

Blocking

  • Dual SIGKILL escalation paths (process_runner.cjs:334): when runtimeGuard fires, both postResultWatchdogTimer and runtimeGuardTimer can independently escalate to SIGKILL. While Node.js single-threading prevents an actual double-kill via the sentSigkillAt guard, the termination grace period applied is determined by whichever timer polls first — non-deterministic behavior for a safety feature. See inline comment.

Non-blocking

  • Silent error swallowing in readWorkingSetFromTokenUsage (codex_harness.cjs:75): if calculateWorkingSetFromJSONL throws, the circuit breaker silently disables itself for the run. Add diagnostic logging in the catch block.

  • Off-by-one at threshold boundary in evaluateContextRebuildCircuitBreaker (codex_harness.cjs:93): rebuildFactor < maxRebuildFactor means rebuild_factor === maxRebuildFactor does not trip the breaker. Clarify whether this is intentional or use <=.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · copilot · sonnet46 · 41.3 AIC · ⌖ 9.57 AIC · ⊞ 6.2K

Comments that could not be inline-anchored

actions/setup/js/process_runner.cjs:334

Dual SIGKILL escalation paths for the runtime guard — redundant and fragile

When runtimeGuard fires SIGTERM and both postResultWatchdogTimer and runtimeGuardTimer are running, the SIGKILL escalation is handled by both timers. The modified watchdog branch (line 334) now selects runtimeGuardTermGraceMs when runtimeGuardFired, and runtimeGuardTimer also independently checks and fires SIGKILL (lines 347–351). Both check sentSigkillAt === 0 before acting, so there is no double-k…

actions/setup/js/codex_harness.cjs:75

readWorkingSetFromTokenUsage silently swallows all errors — consider structured logging

The catch block in readWorkingSetFromTokenUsage uses a bare continue with no logging. If calculateWorkingSetFromJSONL throws an unexpected error (e.g., a JSON parse bug, OOM on a huge file), the circuit breaker silently returns null and is disabled for the rest of the run with no diagnostic trace. Given that this is a safety feature, silent failure is the worst possible mode.

A minimal im…

actions/setup/js/codex_harness.cjs:93

evaluateContextRebuildCircuitBreaker: rebuildFactor &lt; config.maxRebuildFactor returns early — boundary value is not checked

Line 93: rebuildFactor &lt; config.maxRebuildFactor means a rebuild factor exactly equal to maxRebuildFactor does NOT trip the breaker (returns false). The default value is 25, so a rebuild_factor of 25.00 would pass through unchecked while 25.01 trips it. This is likely unintentional — standard circuit breaker semantics use &gt;= (trip at or above the lim…

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

gh-aw-pr-sous-chef
@copilot PR Sous Chef triage: this PR still needs a forward-progress pass before maintainer investigation is likely to be productive.

Please do one focused follow-up pass:

  • address the current blocking github-actions reviews requesting changes on token-usage path selection, runtime-guard/SIGKILL grace-period handling, and the circuit-breaker boundary/error-handling issues called out in the latest review set
  • reply in the relevant review threads with the concrete fix references, push the updates, and rerun the relevant checks
  • refresh the branch if needed and use the pr-finisher skill before handing back for review

Run: https://github.com/github/gh-aw/actions/runs/32800975910

Generated by 👨🍳 PR Sous Chef

Generated by 👨‍🍳 PR Sous Chef · pi · gpt54 · 20.9 AIC · ⌖ 8.06 AIC · ⊞ 8.7K ·
Comment /souschef to run again

…, and SIGKILL escalation

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
…dependent grace periods

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

gh-aw-pr-sous-chef
@copilot PR Sous Chef triage: this PR still needs a forward-progress pass before maintainer investigation is likely to be productive.
...

Forward-progress pass done. Addressed the blocking review threads:

  • Token-usage path selection — candidates are now stat'd, ordered newest-first by mtime, and any whose parsed measurement_state is "unavailable" is skipped, so neither path ordering nor a stale/malformed file can mis-drive the breaker.
  • Runtime guard / SIGKILL grace period — watchdog and guard now track separate SIGTERM/SIGKILL timestamps, and the guard escalates via a dedicated setTimeout(termGraceMs) rather than waiting for the next (up to 15s) poll tick.
  • Circuit-breaker boundary/error handlingGH_AW_CODEX_REBUILD_MIN_CUMULATIVE_INPUT_TOKENS values that floor below 1 fall back to the safe default, GH_AW_CODEX_MAX_REBUILD_FACTOR=1 is now accepted, and a guard-fired attempt that exits 0 (Codex handling SIGTERM cleanly) is normalized to exit code 1 so it can never be reported as a successful run.
  • Async polling — the token-usage read path is now async so guard polls don't block the driver event loop.

Replies with details were posted in each thread. Tests: 173 passing across codex_harness.test.cjs / process_runner.test.cjs, plus 399 passing across the other harness suites; new coverage includes threshold boundaries, mtime freshness selection, async-guard non-overlap, SIGKILL escalation timing, and an end-to-end guard-fired clean-exit case.

Copilot AI requested a review from gh-aw-bot August 25, 2026 02:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[deep-report] Codex engine driver_exit collapse (49.4% success) — add context-rebuild circuit breaker

4 participants