Skip to content

fix(runtime): Codex route checks follow openai_base_url the way Codex does, and -smithers paths stay intact in diagnostics - #1210

Merged
aviggiano merged 2 commits into
mainfrom
claude/g2-runtime-greptile-fixes
Sep 29, 2026
Merged

aviggiano merged 2 commits into
mainfrom
claude/g2-runtime-greptile-fixes

Conversation

@aviggiano

@aviggiano aviggiano commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • A Codex openai_base_url with no model_provider was acknowledged as model:openai (Greptile on #1173). codexRouteConfig (added in fix(runtime): agent adapters never hang or reroute on telemetry and environment quirks #1173) reported no route unless a model_provider line existed. A config.toml holding only openai_base_url = "<gateway>" was therefore planned and acknowledged as model:openai, and a later edit of that URL still passed the per-invocation route check. When nothing selects a provider, Codex uses its built-in openai provider, and openai_base_url redirects it. A live Codex 0.158.0 run whose config set only openai_base_url = "http://127.0.0.1:47211/v1" sent all 13 /v1/responses requests there, each with the bearer key. The gap predates fix(runtime): agent adapters never hang or reroute on telemetry and environment quirks #1173: the earlier codexConfigAffectsRoute regex never matched openai_base_url. fix(runtime): agent adapters never hang or reroute on telemetry and environment quirks #1173 listed it under "Deliberately not built" as a follow-up.
  • Editing an unused openai_base_url stopped every later Codex task (Greptile on #1173). When model_provider selected a custom provider, openai_base_url was still digested, even though it redirects only the built-in openai provider. In a live run with a custom base_url on :47212 and openai_base_url on :47211, Codex sent 6 requests to :47212 and none to :47211. Editing or deleting that leftover line mid-campaign changed the route ID. Every later Codex task then failed in buildCommand with effective CodexAgent provider route changed after disclosure acknowledgement, although traffic still went to the same endpoint. fix(runtime): agent adapters never hang or reroute on telemetry and environment quirks #1173's field-level digest still included openai_base_url for every selected provider; before fix(runtime): agent adapters never hang or reroute on telemetry and environment quirks #1173 the whole file was hashed, so any edit failed the same way.
  • Operator diagnostics rewrote a path ending in -smithers (Greptile on #1194). publicWorkflowText renames the runner's own smithers <command> suggestions before the path-preserving scrub runs. fix: observers and benchmark harnesses survive transient failures and report real paths #1194's lookbehind skipped only /, \ and ., so the rename still fired after -, @, +, ~ and similar characters. spawn /opt/runner/pre-smithers ENOENT became spawn /opt/runner/pre-workflow runner ENOENT, and Command failed: /opt/tools/pinned-smithers why --run-id r1 became Command failed: /opt/tools/pinned-`ultrafuzz why` --run-id r1. The exposure is narrow. On Linux the runner runs through /proc/<pid>/fd/<n> paths, so a failed runner query does not name the runner's own path. A probe with a failing runner at <dir>/pinned-smithers reported Command failed: /proc/<pid>/fd/29 /proc/<pid>/fd/28 why .... A missing runner reads ENOENT: ... open '<path>', which the rename leaves alone. The rewrite still hits any other text that reaches publicWorkflowText: runner stderr, why prose, event labels and details, and node errors.

Change

  • codexRouteConfig (packages/runtime/src/data-governance.ts): when nothing selects a provider but openai_base_url is non-empty, the selected provider falls back to "openai". Codex 0.158.0 drops an empty openai_base_url and uses its default endpoint (codex-rs/core/src/config/mod.rs: .filter(|value| !value.is_empty())), so openai_base_url = "" with no model_provider stays model:openai, as on main. openai_base_url is digested only when the selected provider is openai. Otherwise it is null, the value a config without the key already had. docs/security.md, the function's doc comment and the inline comment now describe this rule.
  • publicWorkflowText (packages/runtime/src/lifecycle-inspection.ts): the rename's \b(?<![\\/.]) becomes (?<=^|[\s"'`([]). The rename now fires only where smithers starts a word: at the start of the text, or after whitespace, a quote, a backtick, ( or [. scrubWorkflowRunnerText still replaces the name in prose.
  • Source: +10/−6 lines. Three code lines change; the rest are comments. No new functions.

Overlaps with in-flight work

Deliberately not fixed

  • doctor walks every retained controller root with no limit (Greptile on #1179). In-flight x05 already fixes this (origin/claude/x05-typed-resume-result, dd04ef2 "doctor stops sizing leftover controller roots after a second"), so it is not repeated here.

Follow-up (needs a GitHub issue; this branch opens none)

  • API-key preflight for a config with only openai_base_url. This is not a Greptile finding and is not changed here. addCodexProviderRoute (templates/smithers/agents/codex.tsx) sets OPENAI_BASE_URL only from a model_provider table. With only openai_base_url set, it leaves OPENAI_BASE_URL unset, and Smithers' key preflight probes the inherited or default endpoint with the configured key instead of the gateway Codex will use. That is the case the function's own comment says it prevents: "an ambient endpoint can receive the configured provider's credential during preflight". Traced in code, not run. Smithers logs failed preflight probes as non-blocking (seen in the Bun contract run), so this would send a credential to the wrong endpoint but would not stop a campaign.

Verification

Discriminating tests (runtime, node:test), on this branch rebased onto origin/main 142ba80. Each variant below patches one compiled module in a copy of dist-test and runs the named test.

  • data-governance.test.ts › "Codex CLI bookkeeping in config.toml does not change the acknowledged route" gains three assertions:

    • an openai_base_url line inside the "nothing else in the file" custom-provider config;
    • an openai_base_url-only config, which must equal the explicit model_provider = "openai" form;
    • openai_base_url = "", which must stay model:openai.

    Results:

    • origin/main: fails at the custom-provider assertion with + 'model:codex-route-ce49f638…' - 'model:codex-route-b40823df…'.
    • Only the openai_base_url ternary: fails at the implicit-openai assertion with + 'model:openai' - 'model:codex-route-2a585365…'.
    • Only the default selection: fails at the custom-provider assertion, as on main.
    • The pre-review === undefined default selection: fails at the empty-URL assertion with + 'model:codex-route-b43c6ddf…' - 'model:openai'.
    • This branch: passes. The whole file passes 13/13.
  • lifecycle-inspection.test.ts › "diagnoseRun keeps a runner path intact in public text" gains the note spawn /opt/runner/pre-smithers ENOENT.

    • origin/main's regex: fails with + 'spawn /opt/runner/pre-workflow runner ENOENT' - 'spawn /opt/runner/pre-smithers ENOENT'.
    • This branch: passes.
  • lifecycle-inspection.test.ts › "diagnoseRun adapts the engine diagnosis without engine-branded public text" gains the note then run smithers inspect, expected as then run `ultrafuzz inspect` . This pins the whitespace case of the new lookbehind, the most common form in the engine's own text. main renames it too, so the test passes on main by design.

    • Lookbehind narrowed to (?<=^|[`]): fails with + 'then run workflow runner inspect' - 'then run `ultrafuzz inspect`'.
    • The ", ', ( and [ cases remain unpinned by tests. The engine scan below covers them.

Regression and behaviour checks:

  • lifecycle-inspection.test.js:
  • The 7 Bun adapter contract tests matching '^Bun adapter contract: (planned routes equal|quoted TOML provider routes|generated Codex)' pass after the rebase. They include the plan-versus-adapter route equality and drift tests.
  • publicWorkflowText, main's against this branch's (built dist, with the real scrub and secret redaction), over string literals that mention smithers, re-run after the rebase:
  • Route IDs, main's dist against this branch's compiled module:
    • Unchanged:
      • no config.toml;
      • CLI bookkeeping only;
      • an unselected provider table;
      • openai_base_url = "" alone (model:openai);
      • explicit model_provider = "openai" with no URL, with URL A or B, or with "";
      • a custom provider;
      • a moved custom base_url.
    • Changed:
      • A non-empty openai_base_url alone, or with a profile that selects no provider, now gets the explicit openai form's ID for that URL: 3dac300f for URL A, ad647009 for URL B. main gives model:openai.
      • A custom provider plus openai_base_url A, B or "" now gets the ID it has without that line (0ec061c9). main gives 08ee3b6e, dd87cc28 and 12678406.
  • modelDestination in cloud mode (execution.mode = "cloud", provider modal):
    • A host config.toml with only a non-empty openai_base_url: main returns model:openai, and this branch throws cloud execution cannot use host provider-home routing; ….
    • openai_base_url = "" alone: both return model:openai.
  • Live Codex 0.158.0 runs (from before review; fake key, local servers that answer 401):
    • Only openai_base_url set: 13 requests went to it.
    • A custom provider plus openai_base_url: 6 requests went to the custom base_url, none to openai_base_url.
    • A user [model_providers.openai] table: Codex refuses to load it ("model_providers contains reserved built-in provider IDs: openai"). So a selected openai is always the built-in provider.
  • Passed after the rebase:
    • npx prettier --check and npx eslint on the changed files;
    • CI=1 ESLINT_PLUGIN_DIFF_COMMIT=origin/main pnpm -w lint:strict:ci;
    • pnpm -w lint;
    • pnpm --filter @ultrafuzz/runtime typecheck;
    • pnpm -w knip;
    • node scripts/docs-check.mjs.

Risk / compatibility

  • Route IDs change only for Codex configs that set a top-level openai_base_url:

  • Migration for either shape. Add the new ID to ULTRAFUZZ_DATA_GOVERNANCE_POLICY and re-acknowledge. Without the policy row, prepareDataGovernance fails planning with DATA_GOVERNANCE_DESTINATION_NOT_ALLOWED, whatever the policy's sensitivity. Modal public benchmark runs are the exception: they derive their policy from the required destinations.

  • In-flight runs. A run planned before this change with either shape fails every Codex task after an upgrade followed by a plain ultrafuzz resume. This is traced in code, not run end to end:

    1. submitSmithersContinuation sets ULTRAFUZZ_RUNTIME_MODULE to the installed runtime (start-run.ts:585).
    2. The adapters import providerRouteDestination from that module (templates/smithers/agents/environment.tsx:13-20), so they compute the new ID.
    3. The launch governance that the resume restores (authenticatedContinuationGovernancePath, workflow-integrity.ts:432) still lists the old ID.
    4. environment.tsx:248-249 therefore throws effective CodexAgent provider route changed after disclosure acknowledgement.

    Finish such runs before upgrading, or re-plan them. With a custom provider, no config edit restores the recorded ID, because openai_base_url no longer enters that provider's digest. With only openai_base_url set, deleting the line brings back model:openai and lets the resume continue. Codex traffic then goes to OpenAI, the destination the run acknowledged.

  • Cloud planning. With execution.mode = "cloud", planning now rejects a host Codex config that sets only a non-empty openai_base_url ("cloud execution cannot use host provider-home routing; …"). It already rejected one that sets model_provider, and docs/reference/cloud-execution.md documents that rejection. main planned this case as model:openai. The check runs only at plan time: prepareDataGovernance in plan-run.ts is its only caller, so no in-flight run is affected. It becomes moot once draft refactor!: remove per-node cloud execution (execution.mode = "cloud") #1197, which deletes the guard, lands.

  • Diagnostic text. publicWorkflowText only affects diagnostic text, so no run outcome changes. A runner suggestion glued to a preceding character other than whitespace, a quote, a backtick, ( or [ is now scrubbed to workflow runner <command> instead of renamed to `ultrafuzz <command>`.

  • Validator identity. No validator, schema or contract-description files are touched, so VALIDATOR_BUILD_IDENTITY does not change.

Changelog entry

Breaking changes (or fold into the #908 route-ID entry):

  • [runtime] [docs] A Codex config.toml with a non-empty top-level openai_base_url and no model_provider is now acknowledged as its own route (the ID of the explicit model_provider = "openai" form) instead of model:openai, and cloud planning rejects it as it rejects a model_provider config; with a custom model_provider selected, openai_base_url, which Codex then ignores, no longer counts, so editing it no longer fails later Codex tasks with "provider route changed after disclosure acknowledgement". Route IDs change for both config shapes: update ULTRAFUZZ_DATA_GOVERNANCE_POLICY, re-acknowledge, and re-plan rather than resume a run whose acknowledged ID changed (with only openai_base_url set, deleting that line also lets such a run resume, sending Codex traffic to OpenAI as acknowledged).

Other changes:

  • [runtime] Operator diagnostics now keep a path such as /opt/tools/pinned-smithers intact instead of rewriting part of it.

Greptile follow-up

  • Fixed: an emphasized runner command such as **smithers why** was scrubbed to **workflow runner why** instead of becoming `ultrafuzz why` (comment). * is now a word boundary for the rename. The diagnoseRun keeps a runner path intact in public text test gains that case. It fails with the old boundary set, and paths such as pre-smithers still pass through unchanged.
  • Kept: route IDs change on upgrade (comment). fix(runtime): agent adapters never hang or reroute on telemetry and environment quirks #1173, which also ships in v0.1.2, already changes every route ID derived from a provider config file relative to v0.1.1, and it documents that as a breaking change. 0.x releases do not keep runs resumable across such changes; re-plan instead. This PR adds no break for any released version.

Rebased onto main after #1207. Both PRs changed the rename's regex; the rebase keeps this PR's standalone-word boundary, #1207's lowercase-only flags and its comment.

🤖 Generated with Claude Code

RetriggerConfidence Score: 5/5

The PR appears safe to merge; no new actionable issue or outstanding previous finding remains.

Fix All in Claude CodeFindings

  1. P1 Upgrades strand in-flight Codex runs ▶
Fix with agent prompt
### Issue 1
packages/runtime/src/data-governance.ts:undefined-456
A run planned with a custom Codex provider and a top-level `openai_base_url` keeps its original route ID in sealed governance. After an upgrade, a plain resume loads the new runtime, and this line computes a different ID even though the provider endpoint has not changed. The adapter rejects every subsequent Codex task as an unacknowledged route. Preserve compatibility with the recorded ID or provide a way to migrate the run without re-planning it.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

The PR makes Codex route acknowledgement account for a non-empty openai_base_url when no provider is selected, excludes that URL when a custom provider is selected, and preserves -smithers path suffixes in diagnostics. Tests and security documentation cover the changed behavior.

Reviews (2) · Last reviewed commit: "fix(runtime): a path ending in -smithers..."

@aviggiano
aviggiano requested a review from a team as a code owner September 29, 2026 14:39
// Redirects the built-in openai provider when that is the one selected.
openai_base_url: config.openai_base_url ?? null
// Redirects only the built-in openai provider.
openai_base_url: selected === "openai" ? (config.openai_base_url ?? null) : null

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Upgrades strand in-flight Codex runs A run planned with a custom Codex provider and a top-level openai_base_url keeps its original route ID in sealed governance. After an upgrade, a plain resume loads the new runtime, and this line computes a different ID even though the provider endpoint has not changed. The adapter rejects every subsequent Codex task as an unacknowledged route. Preserve compatibility with the recorded ID or provide a way to migrate the run without re-planning it.

Knowledge Base Used: Execution runtime

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/runtime/src/data-governance.ts
Line: 456

Comment:
**Upgrades strand in-flight Codex runs** A run planned with a custom Codex provider and a top-level `openai_base_url` keeps its original route ID in sealed governance. After an upgrade, a plain resume loads the new runtime, and this line computes a different ID even though the provider endpoint has not changed. The adapter rejects every subsequent Codex task as an unacknowledged route. Preserve compatibility with the recorded ID or provide a way to migrate the run without re-planning it.

**Knowledge Base Used:** [Execution runtime](https://app.greptile.com/monad-foudnation/-/custom-context/knowledge-base/monad-developers/ultrafuzz/-/docs/execution-runtime.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code

Comment thread packages/runtime/src/lifecycle-inspection.ts Outdated
aviggiano and others added 2 commits September 29, 2026 15:28
…hen Codex uses it

codexRouteConfig digested the top-level openai_base_url only when a
model_provider line existed, and then for every selected provider.
Codex does neither. With no model_provider it uses its built-in openai
provider, and openai_base_url redirects only that provider. A live
Codex 0.158.0 run with a fake key against local servers showed both:
with only openai_base_url set, all 13 requests went to that URL with
the bearer key; with model_provider = "private" (its own base_url) plus
openai_base_url, all 6 requests went to the private base_url and none
to openai_base_url.

So a config.toml holding only openai_base_url = <gateway> planned and
acknowledged model:openai while Codex sent every task to the gateway,
and a later edit of that URL still passed the per-invocation route
check. With a custom provider selected, editing a leftover, unused
openai_base_url line changed the route ID, so every later Codex task
failed with "effective CodexAgent provider route changed after
disclosure acknowledgement".

The selection now falls back to "openai" when openai_base_url is
non-empty, and openai_base_url is digested only when the selected
provider is openai. Codex 0.158.0 drops an empty openai_base_url and
uses its default endpoint, so openai_base_url = "" with no
model_provider stays model:openai. Configs without openai_base_url keep
their route IDs. An openai_base_url-only config now gets the same ID as
the explicit model_provider = "openai" form, and a custom provider plus
openai_base_url gets the ID it has without that line.

Each changed ID needs a new ULTRAFUZZ_DATA_GOVERNANCE_POLICY row plus
re-acknowledgement, and a run planned with such a config before this
change has to be re-planned rather than resumed. Traced, not run end to
end: native resume points the adapters at the installed runtime, so
they compute the new ID and every Codex task fails the route check
against the run's sealed acknowledgement. Cloud planning now rejects a
host config.toml that sets only a non-empty openai_base_url, as it
already rejected one that sets model_provider.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…gnostics

publicWorkflowText renames the runner's own `smithers <command>`
suggestions before the path-preserving scrub runs. #1194 stopped the
rename from firing after a path separator or a dot, but it still fired
after any other non-word character. A path whose last segment ends in
"smithers" after a hyphen (or @, +, ~, ...) and is followed by a space
and a word was rewritten before the scrub could keep it whole:
"spawn /opt/runner/pre-smithers ENOENT" became
"spawn /opt/runner/pre-workflow runner ENOENT", and
"Command failed: /opt/tools/pinned-smithers why --run-id r1" became
"Command failed: /opt/tools/pinned-`ultrafuzz why` --run-id r1".

Exposure is narrow. On Linux the runner is executed through
/proc/<pid>/fd paths, so a failed runner query does not name the
runner's own path (a probe with a failing runner at
<dir>/pinned-smithers reported "Command failed: /proc/<pid>/fd/29
/proc/<pid>/fd/28 why ..."), and a missing runner is reported as
"ENOENT: ... open '<path>'", which the rename leaves alone. The rewrite
still applies to any other text that reaches publicWorkflowText: runner
stderr, `why` prose, event labels and details, and node errors.

The rename now fires only where "smithers" starts a word: at the start
of the text, or after whitespace, a quote, a backtick, or an opening
parenthesis or bracket. The scrub still replaces the name in prose.
Over the 1,064 string literals that mention "smithers" in
packages/*/src and packages/*/test, main's and the new
publicWorkflowText differ only on this change's own new pre-smithers
strings. Over the 1,664 distinct lines that mention `smithers <word>`
in the pinned Smithers 0.35.0 packages they differ on one, an HTML
<title> template in @smthrs/cli's runReport.js.

The diagnoseRun fixture now also carries a hint after whitespace
("then run smithers inspect"), the form missing from the tests, so
narrowing the lookbehind to the start of the text and backticks fails.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@aviggiano
aviggiano force-pushed the claude/g2-runtime-greptile-fixes branch from c03617b to acf8567 Compare September 29, 2026 15:31
@aviggiano
aviggiano merged commit 13af837 into main Sep 29, 2026
15 of 17 checks passed
@aviggiano
aviggiano deleted the claude/g2-runtime-greptile-fixes branch September 29, 2026 16:16
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.

1 participant