openai / openai/codex-plugin-cc

Stop review gate has no way to pin the model or reasoning effort it reviews with

Open
#769 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
33.3k
Forks
2.3k
PR merge metrics
No merged PRs in 30d

Description

What happens

scripts/stop-review-gate-hook.mjs runs its review by spawning the companion as a task:

// scripts/stop-review-gate-hook.mjs:105  (1.0.6)
const result = spawnSync(process.execPath, [scriptPath, "task", "--json", prompt], {
  cwd,
  env: childEnv,
  encoding: "utf8",
  timeout: STOP_REVIEW_TIMEOUT_MS
});

The invocation passes neither --model nor --effort, so model selection falls through to
normal codex resolution: $CODEX_HOME/config.toml, or ~/.codex/config.toml by default. There
is no gate-specific setting anywhere.

Why that is awkward

The gate fires at the end of every turn, in every workspace where it is enabled, on whatever the
effective codex default happens to be at that moment. On one machine over four days that meant
stop-gate reviews ran on four different models without anyone choosing a reviewer. The only way
to change it is to change the default for every codex invocation on the machine, codex exec and
every other plugin command included.

That blocks ordinary cases: a cheap model in a repository where the gate is a tripwire, a
stronger one where the review is meant to be read, or simply pinning the reviewer so that
changing a global default does not silently redefine what "reviewed" means.

Why it looks like a cheap fix

Everything needed is already present:

  • codex-companion.mjs already accepts --model <model|spark> and
    --effort <none|minimal|low|medium|high|xhigh> on task (usage string at
    scripts/codex-companion.mjs:82), normalizes them at :103 (normalizeRequestedModel, aliases
    at :72) and :115 (effort validation, valid set at :71), and threads both into
    buildTaskRequest({ cwd, model, effort, ... }) at :604, called at :793.
  • The plugin already keeps per-workspace configuration in state.json under a config object.
    stopReviewGate lives there (default at scripts/lib/state.mjs:23), and setConfig at
    scripts/lib/state.mjs:153 writes arbitrary keys into it. loadState and saveState merge
    rather than whitelist, so an added key survives round trips.

Nothing is wired between the two.

Proposed change

A stopReviewModel key (and, if wanted, stopReviewEffort) in the existing per-workspace
config object, unset by default.

Setter, in codex-companion.mjs, beside the existing gate toggle in the setup handler
(:218 declares the boolean options, :229:233 writes stopReviewGate):

if (options["stop-review-model"]) {
  const raw = String(options["stop-review-model"]).trim();
  if (raw === "default") {
    setConfig(workspaceRoot, "stopReviewModel", null);   // see note below
    actionsTaken.push(`Stop-gate reviews for ${workspaceRoot} will use the codex default.`);
  } else {
    const model = normalizeRequestedModel(raw);
    setConfig(workspaceRoot, "stopReviewModel", model);
    actionsTaken.push(`Stop-gate reviews for ${workspaceRoot} will run on ${model}.`);
  }
}

normalizeRequestedModel applies MODEL_ALIASES and passes any other string through, so this
does not constrain which models are nameable. That is deliberate: the plugin does not maintain a
model allowlist elsewhere either.

Reader, in stop-review-gate-hook.mjs. runStopReview(cwd, input) at :98 does not have the
config in scope, but the caller does: main() already calls getConfig(workspaceRoot) at :146
for the stopReviewGate check at :152. Either pass the model in or read it inside:

function runStopReview(cwd, input = {}, model = null) {
  ...
  const args = model
    ? [scriptPath, "task", "--json", "--model", model, prompt]
    : [scriptPath, "task", "--json", prompt];
  const result = spawnSync(process.execPath, args, { cwd, env: childEnv, ... });

with the caller passing the config.stopReviewModel it already holds.

Documentation, one line in commands/setup.md beside --disable-review-gate.

Notes for whoever implements it
  • Clearing. Writing null works with the reader above because null is falsy, but then
    "absent key" and "key set to null" mean the same thing by accident. If state.mjs grows an
    unset operation, use it; otherwise the null-means-inherit-default convention should be written
    down rather than left to the reader.
  • Scope. Small and localised, but not one line: configuration, command surface,
    documentation, plus tests worth having. Unset keeps current behaviour; an explicit model reaches
    the task request; default clears it; two workspaces hold independent values; a state file
    written before the change still loads.
  • Shape of the key. Flat stopReviewModel alongside stopReviewGate is the smaller patch. A
    nested stopReviewGate: { enabled, model } would read better but needs a migration, and is only
    worth it if structured config is already the direction.
  • --effort is the adjacent knob. task already validates
    none|minimal|low|medium|high|xhigh. If the motive for pinning a model is cost, effort is
    usually the larger lever, and the same key shape works for it.
Separate issue, mentioned so the two do not get merged

When the review cannot run at all (expired credentials, for instance), the gate receives
status: 1 with empty rawOutput and reports a failed review. An unavailable reviewer and an
adverse verdict are presented identically, so a credentials problem reads as a finding and blocks
the turn until the operator works out what it was. Distinguishing "reviewer ran, adverse verdict"
from "reviewer could not produce a verdict", and stating a policy for the second, is a
correctness change that does not belong in the model patch above.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the setup handler in scripts/codex-companion.mjs, the configuration helpers in scripts/lib/state.mjs, and runStopReview/main in scripts/stop-review-gate-hook.mjs. Read commands/setup.md for the documented setup options and inspect the existing configuration and task-request tests, if present. Done means workspace-specific model selection is persisted, reaches stop-gate task execution, can be cleared to restore the default, and is documented without changing existing behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
tooling
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.