Skip to content

Grok CLI providers silently drop a saved thinking-effort level #5854

Description

@atomantic

Problem

Grok Build CLI accepts a reasoning-effort flag, but PortOS never emits one for it. A user can save an effort level against a grok provider (a CoS task pin, a pipeline stage, a scheduled task) and it is silently dropped at spawn time — and the effort select is hidden in the picker, so there is no UI that reveals the level is inert.

The cause is effortLevelsForProvider (server/lib/providerModels.js:356), which has ladder entries for opencode-local, codex, antigravity, cursor and claude and returns null for everything else. resolveCliEffort short-circuits on a null ladder, so buildEffortArgs returns [] and no flag is injected. The client mirror (client/src/utils/providers.js:566) has the same gap, which is why the select disappears.

Surfaced while making the pr-reviewer pipeline stages provider-agnostic (#5830): a grok-only install can now configure every stage's provider and model, but its thinking effort is inert.

Decision (already made — do not re-litigate)

Add a grok ladder. The values are not a guess — the CLI itself enumerates them:

$ grok --reasoning-effort bogus -p hi
--effort/--reasoning-effort: unknown effort level 'bogus'; use one of: xhigh, high, medium, low

So: GROK_EFFORT_LEVELS = ['low', 'medium', 'high', 'xhigh'].

No argv change is needed. grok --help documents --reasoning-effort <EFFORT> [aliases: --effort], and buildEffortArgs already emits ['--effort', level] for every non-codex/non-cursor vendor. Adding the ladder is what makes that path fire.

Detect grok inline, do not import grok.js. server/lib/grok.js already imports providerModels.js (line 28), so importing back would create a cycle. Mirror the existing isCodexProvider shape (providerModels.js:216) instead:

export function isGrokProvider(provider) {
  const id = String(provider?.id || '').toLowerCase();
  return id === 'grok-cli' || id === 'grok-tui' || commandBasename(provider?.command) === 'grok';
}

(GROK_CLI_ID / GROK_TUI_ID in grok.js are grok-cli / grok-tui; grok alone is the API provider id, which has no CLI effort flag — leave it out.)

Scope

  1. server/lib/providerModels.js

    • Add GROK_EFFORT_LEVELS = Object.freeze(['low', 'medium', 'high', 'xhigh']) beside the other ladders (~line 90-106).
    • Add isGrokProvider as above.
    • Add the isGrokProvider(provider) arm to effortLevelsForProvider (~line 356).
    • ALL_EFFORT_LEVELS (~line 111) already contains every one of these values via the claude ladder — confirm, and add the spread only if that stops being true.
  2. server/lib/providerModels.jshasEffortFlag (~line 417)
    Teach it grok's long form. It currently recognizes --effort / --effort= / model_reasoning_effort=, so a user who bakes --reasoning-effort high into provider.args would get a second injected --effort <level> appended. Grok's clap parser accepts the duplicate without error and last-wins, so the user's explicit pin is silently overridden — the opposite of the documented "a baked pin wins and the runner-injected effort is suppressed" contract in that function's own doc comment. Match --reasoning-effort and --reasoning-effort= alongside the existing forms.

  3. client/src/utils/providers.js — mirror both the ladder constant and the effortLevelsForProvider arm (~line 355-367 and ~566). Keep the existing "sanitized inventories omit command" fallback in mind: gate on the provider id as well as the command basename, exactly as the server predicate above does.

  4. Tests

    • server/lib/providerModels.test.js: effortLevelsForProvider returns the grok ladder for a grok-cli provider, for a path-configured command (/Users/x/.grok/bin/grok), and for grok-tui; returns null for the grok API provider. resolveCliEffort clamps an out-of-range level down the ladder (max and ultra -> xhigh, minimal -> low), matching the clamp contract already tested for agy.
    • buildEffortArgs emits ['--effort', <level>] for a grok provider, and emits nothing when the argv already carries --reasoning-effort.
    • client/src/utils/providers.test.js: the same ladder assertions on the mirror. There is an existing server/client mirror-parity expectation for these ladders — keep both sides in lockstep or that test fails.

Acceptance criteria

  • Saving xhigh on a grok provider (task pin, pipeline stage, or scheduled task) renders in the effort select and reaches the spawned CLI as --effort xhigh.
  • A provider whose saved args already contain --reasoning-effort <level> gets no second injected flag.
  • The grok API-type provider still has no effort ladder.
  • Server and client ladders stay identical; the existing mirror-parity test passes.

Activity

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

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions