Skip to main content
Reference Last updated: 8 October 2026

Gemini Runner

Nathan Schram
By Founder, Little Bear Apps

Deprecated

The gemini engine is deprecated and no longer supported (as of v0.36.0): it still loads, but gets no fixes, is excluded from testing, and may be removed in a future release (#722). Google ended Gemini CLI support for individual accounts (free, Google AI Pro and Ultra) on 18 June 2026 (announcement; IneligibleTierError); under Untether the subprocess hangs until the watchdog cancels the run. Enterprise / Google Cloud licences are unverified. Antigravity CLI ships as a separate engine in v0.36.1 (#558), not a rename.

Below is the implementation spec for the Gemini CLI runner shipped in Untether.


Scope

Goal

Provide the gemini engine backend so Untether can:

  • Run Gemini non-interactively via the Gemini CLI (gemini).
  • Stream progress by parsing --output-format stream-json (newline-delimited JSON). Each line is a JSON object with a type field.
  • Support resumable sessions via --resume <session_id>.

Non-goals (v1)

  • Plan mode interaction — Gemini supports enter_plan_mode/exit_plan_mode tools but these require interactive stdin.

UX and behavior

Engine selection

  • Default: use default_engine from config
  • Override: /gemini <prompt> in Telegram

Resume UX (canonical line)

Untether appends a single backticked resume line at the end of the message:

`gemini --resume abc123def`

Notes:

  • The resume token is the session id (short alphanumeric string, e.g., abc123def), captured from the init event’s session_id field.
  • Replies use the explicit session id; /continue passes --resume latest.

Non-interactive runs

The runner invokes:

gemini [--resume <session_id>|latest] --output-format stream-json [--model <model>] --approval-mode <mode> [--skip-trust] --prompt=<prompt>

Flags:

  • --prompt=<value> — non-interactive (headless) mode; --prompt is the long form of -p, so no separate -p is passed
  • --output-format stream-json — JSONL output
  • --model <model> — optional, from config or /config override
  • The prompt is bound directly to --prompt= (and space-prefixed if it starts with -), so it is never read as a flag
  • --resume <session_id> — when resuming a session
  • --approval-mode <mode> — defaults to yolo (full access) when no override is set; configurable via /config or permission_mode run option
  • --skip-trust — passed by default as of v0.35.3 (#471) so headless runs work outside ~/.gemini/trustedFolders.json. Gemini CLI rejects runs from any directory not in the trust list — even with --approval-mode yolo — and there is no interactive prompt path in headless usage, so projects outside the trust list previously failed silently before any agent output. Set [gemini] skip_trust = false in untether.toml to opt out (security-conscious operators who want Gemini’s project-local extension/MCP trust gate enforced).

Config additions

untether config

untether config set default_engine "gemini"
untether config set gemini.model "gemini-2.5-pro"

toml

# ~/.untether/untether.toml

default_engine = "gemini"

[gemini]
model = "gemini-2.5-pro"   # optional; passed as --model
skip_trust = true          # optional; default true — opt out to enforce trustedFolders.json

Notes:

  • Gemini auto-routes between Pro (planning) and Flash (implementation) when no model is specified.
  • Authentication is handled by the Gemini CLI itself (Google AI Studio or Vertex AI).

Code changes (by file)

src/untether/runners/gemini.py

Exposes BACKEND = EngineBackend(id="gemini", build_runner=build_runner, install_cmd="npm install -g @google/gemini-cli").

Runner invocation

gemini [--resume <session_id>|latest] --output-format stream-json [--model <model>] --approval-mode <mode> [--skip-trust] --prompt=<prompt>

Event translation

Gemini JSONL output uses a type discriminator field. The runner translates:

  • init -> StartedEvent (captures session_id and model)
  • tool_use -> ActionEvent (phase: started)
  • tool_result -> ActionEvent (phase: completed)
  • message (role=assistant) -> text accumulation for final answer
  • result -> CompletedEvent (with usage from stats)
  • error -> CompletedEvent (ok=false)

Tool name normalisation

Gemini uses snake_case tool names. The runner normalises them via _TOOL_NAME_MAP:

Gemini toolNormalised
read_fileread
edit_fileedit
write_filewrite
web_searchwebsearch
web_fetchwebfetch
list_dirls
find_filesglob
search_filesgrep

Installation and auth

Install the CLI globally:

npm install -g @google/gemini-cli

Run gemini once interactively to authenticate with Google AI Studio or Vertex AI.


Known pitfalls

  • Gemini has no --stream-json-input mode, so interactive features (approve/deny, plan mode toggle) are not possible in headless mode.
  • --approval-mode controls tool access in headless mode. Untether defaults to yolo (full access — all tools auto-approved) when no override is set, since headless mode has no interactive approval path. Without this default, Gemini’s CLI read-only mode disables write tools (run_shell_command, write_file, edit_file), causing most tasks to stall as the agent cascades through sub-agents. Users can restrict via /config → Approval mode: edit files (auto_edit, blocks shell but allows file operations) or read-only (denies most tool calls).
  • Tool names are snake_case (e.g., read_file) unlike Claude Code’s PascalCase — the runner normalises these.

See also

Was this helpful?

Related Articles