Dev.to AI 🤖 Ai 👁 0 📖 5 min read

29,139 vs 30,370: Gemini CLI 0.63 Ships 2 Plan-Mode Prompts, and Your TTY Picks Which

There is an old gemini-cli bug that reads like a model problem and isn't one. Issue #28913: run gemini -p "..." -y in a script, let the agent drift into Plan Mode, and the run ends by itself. No error, no crash — the mod

29,139 vs 30,370: Gemini CLI 0.63 Ships 2 Plan-Mode Prompts, and Your TTY Picks Which

There is an old gemini-cli bug that reads like a model problem and isn't one. Issue #28913: run gemini -p "..." -y in a script, let the agent drift into Plan Mode, and the run ends by itself. No error, no crash — the model emits one paragraph of text, calls no tools, and the process exits. In non-interactive mode a text-only response is a termination signal, so the agent politely waiting for approval looks exactly like "I'm done."

The fix shipped in 0.63 as #29539, fix(core): enable autonomous plan execution in non-interactive mode. I wanted to see what the fix actually changed, so I pointed 0.63.0 at a local mock endpoint and captured the real systemInstruction twice — once with no TTY, once inside one.

Same binary. Same --approval-mode plan. Different prompt.

Plan Mode, Step 2 and Step 4, side by side, captured from the real request bodies

  • no TTY (gemini -p "..."): 29,139 characters
  • inside a pty (script -qec "gemini -i ..."): 30,370 characters

That is a 1,231-character gap in the instructions the agent starts with, decided by nothing but whether stdin is a terminal.

How I captured it

No API key, no model. gemini-cli reads its endpoint from GOOGLE_GEMINI_BASE_URL, so I wrote a small HTTP server that accepts the request, dumps the body, and answers with a canned chunk. Then:

npm i -g @google/[email protected]
gemini --version   # 0.63.0

# ~/.gemini/settings.json
# { "security": { "auth": { "selectedType": "gemini-api-key" } } }
export GEMINI_API_KEY=fake
export GOOGLE_GEMINI_BASE_URL=http://127.0.0.1:8799

# run 1: no TTY
gemini -p "Plan a small refactor of this repo" --approval-mode plan --skip-trust

# run 2: same thing, but give it a terminal
script -qec "gemini -i 'Plan a small refactor of this repo' --approval-mode plan --skip-trust" /dev/null

Both runs hit the same model id (gemini-3.1-pro-preview-customtools:streamGenerateContent) and both bodies landed in my server's log. The systemInstruction string is what I diffed. script -qec is the important trick: it allocates a pty, so the CLI believes it is interactive, and it changes the prompt it builds.

What actually differs

The planning workflow is a rendered template, and it branches on interactivity.

non-interactive (29,139) interactive (30,370)
Plan Mode goal "create a design document before proceeding autonomously" "get user approval before editing source code"
Step 2 heading ### 2. Determine Strategy ### 2. Consult
Step 2 body "you are running non-interactively, proceed directly to Step 3 (Draft) autonomously without halting or waiting for user confirmation" "then STOP and wait for the user to confirm agreement before drafting the plan"
Step 3 (absent) - **Alignment Check:** — present the draft, "ask for feedback or confirmation"
Step 4 "Use the built-in exit_plan_mode tool to finalize the plan... When called, this tool will present the plan and begin implementation." "ONLY use the built-in exit_plan_mode tool ... AFTER you have reached an informal agreement"
Handoff rule "For Directives, you must work autonomously as no further user input is available." "For Directives, only clarify if critically underspecified; otherwise, work autonomously."
Tools in the table 10 13

The interactive prompt is the one that carries the fatal sentence: "CRITICAL:* You MUST NOT proceed to Step 3 (Draft) or Step 4 (Review & Approval) in the same turn as your initial strategy proposal. You MUST wait for user feedback."* That line, plus a mode where a text-only reply ends the run, is #28913 in one paragraph. The non-interactive prompt deletes it and replaces the goal with "proceed autonomously".

The extra three tools in the interactive table are activate_skill, ask_user, and web_fetch. The non-interactive prompt also drops the ask_user mention inside the shell-command rule. Consistent with the issue thread: ask_user is denied in headless sessions anyway, so the old blocking behaviour never went through ask_user — it went through a plain text response with no tool call, which nothing intercepts.

Confirming it's in the artifact, not just main

The PR changes renderPlanningWorkflow() in packages/core/src/prompts/snippets.ts to branch on options.interactive: Step 2 becomes Determine Strategy vs Consult, Step 4's closing moves from formally request approval. to begin implementation., and the Alignment Check line only renders in the interactive branch.

I grepped the strings out of the installed @google/[email protected] package rather than trusting the merge status. Both variants are present in the shipped bundle, in the shape the diff predicts. The fix is in the bytes I ran, not only on the default branch.

The part that matters if you script this thing

The prompt you get depends on whether stdin is a TTY. That is a sharp edge for CI.

  • gemini -p "..." and echo "..." | gemini get the autonomous prompt. Good.
  • Anything that hands the process a pty gets the interactive prompt — the one that says stop and wait. A wrapper using script, pty.fork(), expect, some docker run -it, or a terminal-multiplexing runner will get 1,231 extra characters telling the agent to wait for a human who is never there, in a mode where waiting silently ends the run.

If you built a headless runner on top of gemini-cli and it hangs or exits early on planning tasks, check whether you're allocating a TTY before you check anything else. That's the first thing I'd look at.

What I did not verify

The honest boundary: I captured prompt construction, not model behaviour. My endpoint returns a canned response, so I can prove which instructions the CLI sends, and I can prove the branch is in the shipped bundle. I cannot prove that a real Gemini model now calls exit_plan_mode on its own — that needs a real key and a real run, which I didn't do here. Google's docs for Plan Mode state that non-interactive sessions auto-approve enter_plan_mode/exit_plan_mode and switch to YOLO when exiting Plan Mode; I'm quoting that, not measuring it.

I also didn't capture the 0.62 prompt, so "0.63 fixed it" is based on the changelog entry and the code diff, not a before/after run of both versions.

Two traps if you try this yourself

GEMINI_API_KEY alone is not enough. The CLI refuses to boot until security.auth.selectedType names a real method. "gateway" gets you Invalid auth method selected. (exit code 41); the accepted values are oauth-personal, compute-default-credentials, gemini-api-key, vertex-ai.

Don't capture with -y. YOLO mode fires a gemini-3.5-flash-lite:generateContent "Task Routing AI" complexity-scoring call before the main request. My canned server can't produce a score, and the CLI retried that call five times and then sat there. Use --approval-mode plan without -y when you only want the prompt. Interactive mode also makes one extra flash-lite call for session title/summary.

Both captures are saved as plain text: 29,139 and 30,370 characters, 59,509 in total. The only difference in how I invoked the CLI was whether a pty was attached.

📰 Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.