CodeVetter · Repo Unpack · Behavior traces
google-gemini/gemini-cli
Traced three flows end-to-end. (1) Startup: parent spawn → settings → args → session resolution → partial config → auth → optional sandbox relaunch → full config init → interactive Ink UI or headless loop. (2) Headless agentic turn: input normalization → hooks → GeminiClient stream → Turn/GeminiChat → tool requests → Scheduler (policy→confirm→hooks→execute) → responses fed back until Finished. (3) Session persistence/resume: JSONL transcript written per message/tool call and rehydrated via SessionSelector or imported from file.
- Startup trace (parent): `gemini` bin → packages/cli/index.ts run() → getMemoryNodeArgs() reads ~/.gemini/settings.json for advanced.autoConfigureMemory → getSpawnConfig() → spawn child node with same args + IPC channel → forward admin-settings → on child close, exit with code unless code === RELAUNCH_EXIT_CODE, then respawn.
- Startup trace (child): gemini.tsx main() → setupAdminControlsListener → patchStdio → setupUnhandledRejectionHandler/setupSignalHandlers → SlashCommandConflictHandler.start → loadSettings → optional setupWorktree → parseArguments → resolveSessionId → ConsolePatcher.patch → loadCliConfig (partial, skipExtensions) → validateAuthMethod/validateNonInteractiveAuth → partialConfig.refreshAuth → remote admin settings propagation.
- Startup trace (sandbox hop): if SANDBOX env is unset and not a subcommand, loadSandboxConfig decides; when configured, piped stdin is read and injected into argv as --prompt, then relaunchOnExitCode(start_sandbox(...)) re-executes the CLI inside Docker/Podman/Seatbelt and the outer process exits SUCCESS.
- Startup trace (post-sandbox): full loadCliConfig → config.storage.initialize → createPolicyUpdater(policyEngine, messageBus, storage) → registerCleanup(SessionEnd hook + ConsolePatcher) → flag handlers (--list-extensions/--list-sessions/--delete-session exit early) → setRawMode → setupTerminalAndTheme → initializeApp(config, settings) which auths, connects IdeClient, validates theme, counts GEMINI.md files → startInteractiveUI dynamically imports interactiveCli.js (React/Ink lazy-loaded).
- Headless turn trace: runNonInteractive → promptIdContext.run → ConsolePatcher + TextOutput + Ctrl+C readline keypress abort → new Scheduler({context: config, messageBus}) → optional geminiClient.resumeChat → handleSlashCommand/handleAtCommand → loop: geminiClient.sendMessageStream(parts, signal, prompt_id) → for-await events → collect ToolCallRequest → scheduler.schedule → completedToolCalls → append responseParts to history → next iteration until no tool calls / Finished / stopped.
- Model-stream trace inside core: sendMessageStream → fireBeforeAgentHookSafe (may yield AgentExecutionStopped/Blocked or inject <hook_context>) → processTurn loop → Turn.run → chat.sendMessageStream(modelConfigKey, req, prompt_id, signal, role) → retryWithBackoff around apiCall → per-chunk parse: thought parts → Thought events, text → Content, functionCalls → ToolCallRequest, finishReason → Finished(+citations), citations tracked across chunks.
- Tool-execution trace inside Scheduler: schedule() enqueues SchedulerQueueItem → SchedulerStateManager tracks call lifecycle (validating → awaiting approval → executing → success/error) → checkPolicy consults PolicyEngine (deny short-circuits) → resolveConfirmation publishes ToolConfirmationRequest on the MessageBus for UI approval → evaluateBeforeToolHook may block/modify → ToolExecutor runs the ToolInvocation → ToolCallEvent telemetry logged → CompletedToolCall resolved. (inferred)
- Shell tool trace: model calls run_shell_command → ShellToolInvocation → command parsing via initializeShellParsers/getCommandRoots/stripShellWrapper/detectCommandSubstitution → policy check on root commands → confirmation showing command+roots → ShellExecutionService.execute prefers node-pty (getPty) with childProcessFallback → streamed ShellOutputEvents, live output buffer trimmed at 100k chars, background commands wrapped with EXIT-trap PID capture.
- Session resume trace: --resume → SessionSelector(storage).resolveSession picks or lists JSONL transcripts → ResumedSessionData {conversation, filePath} → in headless mode geminiClient.resumeChat(convertSessionToClientHistory(messages)) rehydrates GeminiChat history; --session-file instead imports an external record, filters to user/gemini messages, mints a new sessionId, and writes a fresh session-*.jsonl.
- AfterAgent continuation trace: when hooks are enabled and no pending tools, fireAfterAgentHookSafe runs; a blocking decision yields AgentExecutionBlocked, decrements hookState.activeCalls, then recursively calls sendMessageStream with the hook's reason as the new request and stopHookActive=true — an internal agentic continuation invisible to the UI loop.
- Interactive UI trace: startInteractiveUI → interactiveCli.js → Ink renders AppContainer (~2900 lines) → useGeminiStream hook drives geminiClient.sendMessageStream and owns Scheduler wiring plus slashCommandProcessor for / commands inside the TUI.