Skip to content

fix: enforce read-only mode for JSON line agents and pass customModel in CLI#563

Open
chr1syy wants to merge 3 commits intoRunMaestro:0.16.0-RCfrom
chr1syy:fix/json-line-agent-readonly-mode
Open

fix: enforce read-only mode for JSON line agents and pass customModel in CLI#563
chr1syy wants to merge 3 commits intoRunMaestro:0.16.0-RCfrom
chr1syy:fix/json-line-agent-readonly-mode

Conversation

@chr1syy
Copy link
Collaborator

@chr1syy chr1syy commented Mar 12, 2026

Summary

  • Read-only mode was silently ignored for all JSON line agents (OpenCode, Codex, Factory Droid) in CLI batch/send mode — spawnAgent never passed the readOnly flag to spawnJsonLineAgent, and the function ignored it anyway
  • YOLO/bypass args overrode read-only flags for all agents — --dangerously-skip-permissions (Claude), --dangerously-bypass-approvals-and-sandbox (Codex), -y (Gemini) were always applied in batch mode, negating read-only enforcement
  • CLI-spawned agents ignored the configured model — the customModel from the desktop UI agent config was never read or passed, causing agents to use their default model instead of the user-configured one
  • OpenCode hung in read-only modereadOnlyEnvOverrides stripped blanket permission grants, causing OpenCode to prompt on stdin (which is closed in batch mode). Fixed to keep "*":"allow" since --agent plan handles read-only enforcement

Changes

File Change
src/cli/services/agent-spawner.ts Pass readOnly and customModel to spawnJsonLineAgent; skip YOLO args in read-only mode for both Claude and JSON line agents; apply readOnlyArgs, readOnlyEnvOverrides, and modelArgs
src/cli/commands/send.ts Pass agent.customModel to spawnAgent
src/cli/services/batch-processor.ts Pass session.customModel to spawnAgent
src/main/agents/definitions.ts Fix OpenCode readOnlyEnvOverrides to keep blanket permission grants
src/shared/types.ts Add customModel to SessionInfo interface

Test plan

  • Verified Claude read-only mode (-r) prevents file modifications
  • Verified Codex read-only mode responds with "Permission denied" instead of modifying files
  • Verified OpenCode read-only mode responds with a plan instead of executing changes
  • Verified OpenCode no longer hangs in read-only batch mode
  • Verified OpenCode uses the configured model from the desktop UI
  • TypeScript type-check passes (no new errors)

🤖 Generated with Claude Code

chr1syy and others added 2 commits March 12, 2026 20:02
spawnAgent was dropping the readOnly flag when calling spawnJsonLineAgent,
and spawnJsonLineAgent ignored it entirely. Now readOnlyArgs and
readOnlyEnvOverrides from agent definitions are applied for all JSON line
agents (Codex, OpenCode, Factory Droid) in read-only/plan mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… in CLI

Three fixes for CLI batch/send agent spawning:

1. Pass readOnly flag to spawnJsonLineAgent (was silently dropped)
2. Skip YOLO/bypass args in read-only mode for all agents — they were
   overriding read-only flags (--dangerously-skip-permissions for Claude,
   --dangerously-bypass-approvals-and-sandbox for Codex, -y for Gemini)
3. Read customModel from agent session config and pass it via modelArgs
   so CLI-spawned agents use the model configured in the desktop UI

Also fixes OpenCode read-only env overrides to keep blanket permission
grants (prevents stdin hangs in batch mode) since --agent plan handles
read-only enforcement at the CLI level.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 12, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e80fd4cb-a0c9-471e-a9ec-890012bd9374

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link

greptile-apps bot commented Mar 12, 2026

Greptile Summary

This PR fixes three independent bugs in Maestro's CLI agent-spawning pipeline: (1) YOLO/permission-bypass args were always applied even in read-only mode, negating -r enforcement; (2) JSON-line agents (OpenCode, Codex, Factory Droid) never received the readOnly flag at all; and (3) the customModel configured via the desktop UI was silently dropped for CLI-spawned agents. The changes are well-scoped and the approach — centralising read-only and model args in AgentDefinition so spawnJsonLineAgent can consume them generically — is consistent with the existing architecture.

Key observations:

  • Breaking existing testsrc/__tests__/cli/services/agent-spawner.test.ts:1179 asserts that --dangerously-skip-permissions is still present when readOnlyMode: true. That assertion was written for the old behaviour where the flag lived in CLAUDE_ARGS; after this PR it will fail because the flag is now only added in non-read-only mode. The test needs to be inverted to not.toContain.
  • Synopsis generation drops customModel — the synopsis spawnAgent call in batch-processor.ts (line 478) was not updated to forward customModel, so for agents like OpenCode and Codex the synopsis invocation will use the agent's default model instead of the user-configured one.
  • Gemini CLI may hang in read-only mode-y is correctly identified as a yoloModeArg and filtered out in read-only mode, but since Gemini has readOnlyCliEnforced: false and no readOnlyEnvOverrides, removing -y leaves no mechanism to prevent interactive prompts, potentially causing the process to hang when stdin is closed (the same root cause as the OpenCode hang this PR fixes).
  • The OpenCode readOnlyEnvOverrides fix (restoring "*":"allow") is correct: --agent plan is the hard CLI boundary for read-only enforcement, and the env config only needs to prevent stdin prompts.

Confidence Score: 3/5

  • Merging carries moderate risk: an existing test will fail (YOLO flag assertion in read-only mode), synopsis generation may silently use the wrong model for OpenCode/Codex, and Gemini read-only mode may hang.
  • The core logic of the PR is sound and addresses real bugs, but three issues lower confidence: (1) a pre-existing test that directly contradicts the new behaviour was not updated, indicating the test suite was not run; (2) the synopsis spawnAgent call was not updated consistently with the task call; (3) Gemini's -y removal is incomplete — the OpenCode fix pattern (keeping permissive env in read-only mode for non-interactive safety) was not applied to Gemini.
  • src/cli/services/agent-spawner.ts needs the Gemini read-only hang concern addressed; src/cli/services/batch-processor.ts synopsis call needs customModel forwarded; src/__tests__/cli/services/agent-spawner.test.ts (not in changeset) needs its YOLO-in-read-only assertion inverted.

Important Files Changed

Filename Overview
src/cli/services/agent-spawner.ts Core fix: moves --dangerously-skip-permissions to a YOLO-only constant, adds readOnlyMode/customModel params to spawnJsonLineAgent, and filters yoloModeArgs in read-only mode — but an existing test (agent-spawner.test.ts:1179) that asserts YOLO flag is present in read-only mode was not updated and will fail; Gemini's -y removal without replacement may also cause batch-mode hangs.
src/cli/services/batch-processor.ts Adds customModel: session.customModel to the task-execution spawnAgent call; synopsis generation call does not forward customModel, which may cause model inconsistency for per-invocation model agents (OpenCode, Codex).
src/cli/commands/send.ts Cleanly adds customModel: agent.customModel to the spawnAgent options; minimal and correct change.
src/main/agents/definitions.ts Restores "*":"allow" in OpenCode's readOnlyEnvOverrides to match defaultEnvVars, preventing stdin hangs in read-only batch mode; readOnlyEnvOverrides is now identical to defaultEnvVars for OpenCode, making it a no-op override but correctly documents the intent.
src/shared/types.ts Adds optional customModel?: string to SessionInfo; straightforward additive type change with no regressions.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[spawnAgent called] --> B{toolType?}
    B -->|claude-code| C[spawnClaudeAgent\ncustomModel silently dropped]
    B -->|usesJsonLineOutput| D[spawnJsonLineAgent\nreadOnlyMode + customModel passed]

    C --> E{readOnlyMode?}
    E -->|yes| F[Add readOnlyArgs\nfrom definitions\nskip CLAUDE_YOLO_ARGS]
    E -->|no| G[Add CLAUDE_YOLO_ARGS\n--dangerously-skip-permissions]

    D --> H{readOnlyMode?}
    H -->|yes| I[Apply readOnlyEnvOverrides\nFilter yoloModeArgs from batchModeArgs\nAdd readOnlyArgs]
    H -->|no| J[Apply all batchModeArgs\nincluding yoloModeArgs]

    I --> K{customModel?}
    J --> K
    K -->|yes| L[Append modelArgs]
    K -->|no| M[Skip modelArgs]

    L --> N[Spawn process]
    M --> N
    F --> N
    G --> N

    style C fill:#ffe0b2
    style I fill:#c8e6c9
    style F fill:#c8e6c9
Loading

Comments Outside Diff (1)

  1. src/cli/services/batch-processor.ts, line 478-483 (link)

    Synopsis spawn doesn't forward customModel

    The task-execution call immediately above correctly passes customModel: session.customModel, but the synopsis call on line 478 does not:

    const synopsisResult = await spawnAgent(
        session.toolType,
        session.cwd,
        BATCH_SYNOPSIS_PROMPT,
        result.agentSessionId   // ← no options, so customModel is omitted
    );

    For agents that accept the model via a CLI flag per-invocation (e.g. OpenCode's --model, Codex's -m), the resumed synopsis call will use the agent's default model rather than the user-configured one. If the configured model is not the agent's default, the synopsis invocation becomes inconsistent with the task invocation and may produce lower-quality or differently-formatted summaries.

    Consider forwarding the model:

    const synopsisResult = await spawnAgent(
        session.toolType,
        session.cwd,
        BATCH_SYNOPSIS_PROMPT,
        result.agentSessionId,
        { customModel: session.customModel },
    );

Last reviewed commit: 85664cb

Comment on lines +27 to +28
// Permission bypass arg for Claude — skipped in read-only mode
const CLAUDE_YOLO_ARGS = ['--dangerously-skip-permissions'];
Copy link

Choose a reason for hiding this comment

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

Existing test asserts YOLO flag is still present in read-only mode

The removal of --dangerously-skip-permissions from CLAUDE_ARGS and its relegation to CLAUDE_YOLO_ARGS (only added in non-read-only mode) is the correct core change of this PR. However, the existing test at src/__tests__/cli/services/agent-spawner.test.ts:1179 explicitly asserts that --dangerously-skip-permissions is still present when readOnlyMode: true:

// From agent-spawner.test.ts, line ~1178-1179
// Should still have base args
expect(args).toContain('--print');
expect(args).toContain('--dangerously-skip-permissions'); // ← contradicts this PR's intent

This assertion was written against the old behaviour where --dangerously-skip-permissions lived in CLAUDE_ARGS and was therefore always included. After this PR it will no longer be present in read-only mode, so the test will fail. The assertion should be inverted:

expect(args).not.toContain('--dangerously-skip-permissions');

Additionally, a new assertion checking it is present in normal (non-read-only) mode would close the gap in coverage.

Comment on lines +386 to +395
// In read-only mode, filter out YOLO/bypass args from batchModeArgs
// (they override read-only flags). In normal mode, apply all batchModeArgs.
if (def?.batchModeArgs) {
if (readOnlyMode && def.yoloModeArgs?.length) {
const yoloSet = new Set(def.yoloModeArgs);
args.push(...def.batchModeArgs.filter((a) => !yoloSet.has(a)));
} else {
args.push(...def.batchModeArgs);
}
}
Copy link

Choose a reason for hiding this comment

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

Gemini CLI may hang in read-only batch mode after -y is stripped

For the Gemini CLI agent, batchModeArgs: ['-y'] and yoloModeArgs: ['-y'] are the same flag. In read-only mode, the new filtering logic strips -y from batchModeArgs:

if (readOnlyMode && def.yoloModeArgs?.length) {
    const yoloSet = new Set(def.yoloModeArgs);
    args.push(...def.batchModeArgs.filter((a) => !yoloSet.has(a)));
}

-y is Gemini's auto-confirm flag; without it Gemini may prompt for interactive confirmation on any tool use or file access. Since stdin is closed immediately after spawning (child.stdin?.end()), this would cause Gemini to receive EOF on its prompt input and either hang or exit with an error.

OpenCode faced the same problem and was fixed by keeping "*":"allow" in its readOnlyEnvOverrides. For Gemini there is no readOnlyEnvOverrides defined, and readOnlyArgs is intentionally empty (readOnlyCliEnforced: false).

One option is to add a Gemini-specific readOnlyEnvOverrides that sets GEMINI_YOLO=1 (or equivalent) to prevent interactive prompts while still relying on the system prompt for read-only enforcement. Alternatively, keep -y for Gemini even in read-only mode (similar to the OpenCode fix) and rely solely on the system prompt to prevent writes.

- Invert stale test asserting --dangerously-skip-permissions is present in
  read-only mode; add coverage for its presence in normal mode
- Skip yolo-arg filtering for agents without CLI-level read-only enforcement
  (Gemini CLI needs -y to avoid interactive prompt hang with closed stdin)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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