Skip to content

review / adversarial-review silently ignore reasoning effort — --effort unparsed, and turn/start effort omitted on the adversarial path #476

Open

Description

@ezraaaa

Summary

review and adversarial-review silently ignore reasoning effort. --effort is not a recognized flag on either command, and adversarial-review omits effort when calling runAppServerTurn — even though the underlying turn/start request already accepts it and task threads it through correctly.

The net effect: the review commands always run at whatever model_reasoning_effort sits in ~/.codex/config.toml, and there is no way to raise effort for a review specifically. For users who treat the adversarial review as their pre-merge quality gate, this is the one place extra reasoning is most worth paying for, and it's the one place they can't buy it.

This is also silent — no "unknown flag" error. ... adversarial-review --effort xhigh is accepted and runs at the config default, which reads as if it worked.

Evidence (v1.0.6)

--model is threaded end-to-end; --effort is missing at every touchpoint on this path.

1. --effort is not parsed — scripts/codex-companion.mjs:714

async function handleReviewCommand(argv, config) {
  const { options, positionals } = parseCommandInput(argv, {
    valueOptions: ["base", "scope", "model", "cwd"],   // <-- no "effort"
    booleanOptions: ["json", "background", "wait"],
    aliasMap: { m: "model" }
  });

Compare handleTask (:764), which has valueOptions: ["model", "effort", "cwd", "prompt-file"] and normalizes via normalizeReasoningEffort.

2. The call site doesn't forward it — scripts/codex-companion.mjs:742

executeReviewRun({
  cwd,
  base: options.base,
  scope: options.scope,
  model: options.model,     // <-- no effort
  ...
})

3. adversarial-review drops it into a call that supports it — scripts/codex-companion.mjs:411

const result = await runAppServerTurn(context.repoRoot, {
  prompt,
  model: request.model,
  sandbox: "read-only",     // <-- no effort
  outputSchema: readOutputSchema(REVIEW_SCHEMA),
  onProgress: request.onProgress
});

…but runAppServerTurn (scripts/lib/codex.mjs:1139) already forwards effort to turn/start:

client.request("turn/start", {
  threadId,
  input: buildTurnInput(prompt),
  model: options.model ?? null,
  effort: options.effort ?? null,   // always null on the review path
  outputSchema: options.outputSchema ?? null
});

So effort arrives as null and the server falls back to the config default.

Two different problems

adversarial-review — straightforward fix. It already routes through runAppServerTurn → turn/start, which accepts effort. Adding "effort" to valueOptions, normalizing it with the existing normalizeReasoningEffort, and passing it through the two call sites should be sufficient.

review (native) — needs a maintainer call. It goes through runAppServerReview → review/start, whose params are { threadId, delivery, target } — no effort field. Model is applied at startThread, but buildThreadParams (lib/codex.mjs:63) doesn't carry effort either. Does thread/start accept a reasoning effort upstream? If so, the fix is symmetric with model. If not, the limitation is worth documenting, because right now it's indistinguishable from a bug.

Repro

node scripts/codex-companion.mjs adversarial-review --wait --scope branch --effort xhigh

Expected: flag rejected, or the run honours xhigh.
Actual: flag silently ignored; the run uses model_reasoning_effort from ~/.codex/config.toml.

Why the usual workarounds don't apply

  • No effort-suffixed model slugs. models_cache.json lists gpt-5.6-sol / -terra / -luna, gpt-5.5, gpt-5.4(-mini), gpt-5.3-codex-spark, codex-auto-review — none encode an effort level, so --model can't stand in for --effort.
  • A [profiles.reviewer] block doesn't work. The companion drives the app-server protocol directly and never passes --profile or reads a profile name, so a reviewer profile is inert config. (This is the same root cause as feat: allow selecting a Codex profile for companion-launched jobs #251, which asks for profile propagation.)

That leaves editing the global model_reasoning_effort as the only lever — which also changes effort for task runs and for the user's interactive Codex sessions, since it's the same file.

Related

Happy to send a PR for the adversarial-review half if that framing is agreeable.

Environment

  • plugin codex@openai-codex v1.0.6
  • codex-cli 0.144.1
  • macOS 25.5.0, Node via npm global
  • auth: ChatGPT account

Activity

  1. axisrow commented on Jul 11, 2026

    @axisrow

    Confirming this for plugin v1.0.6 / codex-cli 0.144.1, with the extra bit that even if you do plumb --effort into the parser and forward it, native review can never honor it — and the reason is worth pinning down so the fix scope is clear.

    I went a level deeper than the parser and probed the protocol/server directly:

    1. ReviewStartParams has no model/effort field. The generated type is { threadId, target, delivery? } (plugins/codex/.generated/app-server-types/v2/ReviewStartParams.ts). effort only exists on TurnStartParams. Assigning the literal to the typed params fails type-checking: TS2353: 'model' does not exist in type 'ReviewStartParams'. So for native review there is simply no field to put effort in — it's not a plumbing gap, it's absent from the protocol.

    2. The real app-server silently drops unknown fields. Direct JSON-RPC probe against codex app-server --stdio (0.144.1):

    • review/start without model/effort → accepted (1 ms).
    • review/start with model + effort → also accepted (1 ms), no validation error.

    The Rust side deserializes without #[serde(deny_unknown_fields)], so unknown fields are ignored rather than rejected. That's exactly why the "silent" behavior in the issue is silent at the RPC layer too — not just at the parser. Adding --effort to the parser + forwarding it (which is what a naive fix does) still results in the field being thrown away server-side for native review.

    3. The model-symmetric fix for effort on native review is blocked by #408. The model path isn't actually working either: model in thread/start/turn/start does not select the model — the app-server boots with the CLI's built-in default and the only effective override is -c model="…" at spawn time (#408). So "thread/start accepts model, do the same for effort" doesn't hold up under testing; even --model on review/task isn't honored today.

    Scoping implication:

    For transparency: I'd opened #477 to forward model/effort to review/start, and I'm closing it as the wrong fix after this check — the fields are inert on the server. Leaving this here in case it saves the next person the same round trip.

  2. axisrow commented on Jul 12, 2026

    @axisrow

    Following up on my earlier comment here with two updates after testing the thread/start.config route (the approach #471 takes): the effort does reach the model that way, and it surfaces a second, related gap worth linking.

    Update to my "native review can never honor effort" claim: I've now verified the thread/start config: { model_reasoning_effort: "high" } route end-to-end against codex-cli 0.144.1. Running a real adversarial-review --effort high (via the plugin companion, fork with --effort wired into spawn -c model_reasoning_effort=) and then reading back the Codex rollout, the review's turn_context entries carry reasoning_effort: "high" — so the override propagates to the actual model turn. That means #471's mechanism (per-thread config, not a dead top-level field) is the viable path, not the review/start field I tried in #477. My "blocked by #408" framing was too pessimistic for effort specifically — #408 is about the boot-default model, which is a separate axis.

    Related gap — #481 (job records never capture resolved model/effort): the reason it took real work to prove the override took effect is exactly what #481 describes. The plugin's jobs/<id>.json stores no resolved model/effort (only threadId/turnId/status), and the only source of truth is the Codex rollout turn_context — which is painful to join: my review ran on 2026-07-12 (job createdAt in UTC) but its rollout landed in the 2026/07/11/ date dir under a session-id that doesn't match the job's threadId. ccusage didn't surface the session at all for the same reason. So #476 (effort ignored) and #481 (effort invisible even when applied) are two faces of the same invisibility: once the override is wired in (#471's route), #481 is what lets anyone confirm it actually ran.

    Net: #471's thread/start.config approach fixes the --effort half of this issue; #481 is the observability piece needed to trust it. Worth linking the three.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions