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
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.
- 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 "..."andecho "..." | geminiget 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, somedocker 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.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.