[Spec 1338] retire-gemini-cli-as-a-builder - #1342
Conversation
Retire the standalone Gemini CLI (`gemini`) builder harness. Google discontinued the Gemini CLI for Pro/Ultra/free tiers on 2026-06-18. Spec recommends a retirement sentinel in the harness resolver so that selecting gemini as a builder — via explicit builderHarness or via command auto-detection — fails closed with a clear retirement message, closing the #929-class silent-fallback-to-Claude footgun. Consult lane (agy / consult -m gemini) is out of scope. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Iteration 1 review: Gemini APPROVE, Codex + Claude REQUEST_CHANGES. All substantive points accepted and incorporated: - Role-agnostic retirement: resolveHarness is shared (no role param) by architect+builder resolution; retire gemini for both roles explicitly. - Two footgun modes made precise (silent Claude fallback at :392 vs undefined/TypeError at :387); sentinel must guard before both. - Reframed as a Codev product retirement (consumer tiers ended 2026-06-18; Standard/Enterprise + API-key remain -> custom-harness escape hatch). - Doctor branch redefined + builder-side flagging; assert structured fields. - afx reset outcome recorded as decided/accepted. - Tests expanded to getBuilderHarness/--builder-cmd/array-form/architect; coverage-by-replacement; doc criterion scoped (exempt historical+consult). Rebuttal: codev/projects/1338-*/1338-specify-iter1-rebuttals.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Approach 1 (retirement sentinel in shared resolver), role-agnostic per architect approval. Three phases: 1. Retire gemini in resolveHarness (core + resolver/config tests) — RETIRED_HARNESSES registry, guard both footgun exits (:387 undefined, :392 Claude default), keep detector, remove GEMINI_HARNESS + registry. 2. Surface cleanly: doctor redefinition + builder-side flagging + spawn/ architect-launch clean-failure handling. 3. README presentation. Governance docs (arch/lessons) in Review phase. agy / consult -m gemini out of scope throughout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Iter-1 review: Gemini APPROVE, Codex REQUEST_CHANGES, Claude COMMENT. All points accepted; restructured 3->4 phases: - Precedence FIX: built-in -> custom -> retired -> unknown (draft wrongly put custom before built-in, which would shadow supported built-ins). - Spawn preflight before worktree/porch creation (spawn.ts resolves harness at :471, after createWorktree/initPorch) -> no orphaned state on reject. - Enumerate 4 getArchitectHarness sites; :291 siblingRegistrationIsLive is a predicate -> guard to return false, not a launch. - Doctor is persisted-config detection, not override-aware. - Name 2nd BUILTIN_HARNESSES consumer harnessProviderFor (reset/context:468). - Add CHANGELOG [Unreleased] breaking-change entry (Phase 4). - Custom gemini escape hatch is explicit-only; auto-detect stays retired. Phases: (1) resolver core+tests (2) fail-closed spawn/launch (3) doctor (4) README+CHANGELOG. Governance docs in Review. Rebuttal: codev/projects/1338-*/1338-plan-iter1-rebuttals.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hared resolver Retire the built-in `gemini` harness (Google ended consumer-tier Gemini CLI availability 2026-06-18). The retirement is role-agnostic: it lives once in the shared resolveHarness and applies to both builder and architect resolution. - Add RETIRED_HARNESSES registry (name -> explanation) plus prototype-safe isRetiredHarness / getRetirement predicates and a throwRetired helper. - Remove GEMINI_HARNESS and its BUILTIN_HARNESSES entry; keep the `gemini` case in detectHarnessFromCommand so the auto-detect path lands on the retirement. - resolveHarness fails closed on both exits: explicit name (builtin -> custom -> retired -> generic "Unknown harness"), and auto-detected command (retired check BEFORE the BUILTIN_HARNESSES lookup). Never a silent Claude fallback (Issue #929 class), never undefined. Explicit custom `gemini` still resolves (escape hatch for retained enterprise/API-key access). - Tests: retirement + escape-hatch + built-in-not-shadowed + predicate coverage (harness, harness-integration); gemini resume guard re-expressed as opencode; config paths (--builder-cmd/--architect-cmd/explicit harness/array-form) all fail closed. Two stale gemini-as-builder comments swept. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surface the gemini harness retirement (Issue #1338) at the spawn and architect-launch boundaries so a retired selection fails closed with no orphaned state and no Tower crash. - spawn.ts: preflight in the spawn() dispatcher (mode !== 'shell') aborts a retired builder harness BEFORE any handler runs. createWorktree itself resolves the builder harness, so guarding above dispatch is what guarantees a rejected gemini spawn leaves zero worktree/porch/db state. - config.ts: assertBuilderHarnessNotRetired — delegates to getBuilderHarness (single source of truth) and rethrows only the retirement; every other resolution error still surfaces at its existing call site. - harness.ts: RetiredHarnessError — a distinct error type so callers can scope a catch to the retirement and rethrow everything else. - tower-utils.ts: siblingRegistrationIsLive catches the retirement and returns false (a retired registration is not live -> reconcile prunes it), so the throw never aborts the whole sibling-reconcile pass. buildArchitectArgs documents the launch-boundary fail-closed throw. - tower-instances.ts: addArchitect converts the retirement throw into a clean { success: false, error } (the add-architect route awaits it without its own try/catch); launchInstance already funnels through its own catch. Tests: assertBuilderHarnessNotRetired (cmd/explicit/array forms; no-op for supported; defers on unknown), siblingRegistrationIsLive -> false for gemini, buildArchitectArgs throws for gemini, launchInstance + addArchitect return a clean retirement result. Full unit suite green (4128 passed / 48 skipped). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…es no state Promote the phase_2 no-orphaned-state proof into a committed regression test. Drives the real spawn() dispatcher against a real temp git workspace whose .codev/config.json selects the retired gemini builder harness (real config loader, no mocks): the spawn rejects with the retirement and leaves zero .builders/ and zero codev/projects state. Tower/GitHub are never reached because the preflight throws before the dispatcher hands off to a handler. Guards the invariant that the preflight runs ABOVE every state-creating handler — createWorktree itself resolves the builder harness, so a guard placed below dispatch would orphan a half-built worktree. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…onnect (Codex iter1) Codex's phase_2 review found two reachable gaps on the restart/reconnect paths — reachable when a config is edited to gemini mid-session, or Tower restarts reading a gemini architect config (initial-launch analysis alone called them unreachable and missed both): 1. Fail-open on reconnect: resolveArchitectRestart propagated RetiredHarnessError, but both tower-terminals consumers (_reconcileTerminalSessionsInner, getTerminalsForWorkspace) caught ALL harness errors and fell back to relaunching cmdParts[0] — the retired gemini binary itself — with no role injection. Extracted the duplicated block into buildArchitectReconnectRestartOptions (tower-utils), which fails closed on retirement (returns undefined: reconnect to a live process if any, never auto-restart into the retired binary) while keeping the plain-command fallback for other harness errors. includeFreshLaunch preserves each site's prior behavior (startup reconcile wires the #1264 clean-exit rerun; on-the-fly reconnect does not). 2. Uncaught throw on clean-exit relaunch: buildArchitectFreshLaunch.next() resolved the harness unguarded; session-manager invokes it with no try/catch, so a clean-exit relaunch of an architect whose config flipped to gemini became an uncaught Tower exception. Guard it: RetiredHarnessError -> log + rerun the original launch args with no harness injection (baseArgs come from the original supported-harness launch, so nothing re-injects the retired harness); rethrow any other error. +7 regression tests (tower-utils.test.ts): both paths fail closed / no-throw for gemini and stay unchanged for codex/claude; non-retirement harness errors still degrade to the plain command. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rebuttal for phase_2 review iteration 1: both Codex REQUEST_CHANGES points (fail-open reconnect relaunch; uncaught clean-exit throw) accepted and fixed in 1152783; Claude's non-blocking deviations recorded for the final review; Gemini/Claude approvals stand. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…f a retired harness (Codex iter2)
Codex's phase_2 iter2 review found the iter1 C2 fix incomplete: returning
{args, env} from buildArchitectFreshLaunch.next() on retirement stops the
throw but NOT the relaunch. SessionManager's clean-exit handler retains the
launch `command` and applies only next()'s args/env — so if the retained
command is itself the retired binary (a custom `gemini` harness later removed,
or a config edit before a clean exit), the retired binary is respawned.
Fail-open.
Fix: give the FreshLaunch contract a `{ stop: true }` signal. On
RetiredHarnessError, next() now returns { stop: true } instead of args/env;
SessionManager's clean-exit handler honors it by ending the session (no
respawn) and surfacing the reason in the pane via session-gave-up ->
PtySession.notice — the same visible-teardown UX as the fast-clean-exit valve.
Tests:
- session-manager.test.ts: end-to-end regression through the real clean-exit
handler with the retained command: "gemini" — asserts spawn is NOT called,
the session is removed, and a `retired` reason is surfaced (the coverage
Codex asked for; the iter1 test only checked returned args).
- tower-utils.test.ts: buildArchitectFreshLaunch.next() for a retired gemini
architect now returns { stop: true }; codex/claude unchanged.
- tower-utils.test.ts: isolate HOME + TOWER_ARCHITECT_CMD in the three
retirement describes (Claude iter2 non-blocking nit) so a dev with either
set locally can't mask the workspace-config fixtures.
FreshLaunch is architect-only (single implementer, single consumer at
session-manager.ts), so the contract addition is low-blast-radius.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rebuttal for phase_2 review iteration 2: Codex's C3 (clean-exit relaunch of the retained retired command) accepted and fixed in 9ec14c4 via a fail-closed FreshLaunch stop signal; Claude's non-blocking test-isolation nit addressed; Gemini/Claude approvals stand. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…th roles + builder side codev doctor's gemini branch claimed "gemini is supported for builders, not architects" — the inverted premise after the #1338 retirement. Redefine it to present the retirement for BOTH roles, and add builder-side detection. - Factor a shared local resolvedShellHarness(role) helper (raw shell.<role> / shell.<role>Harness, array-or-string, via detectHarnessFromCommand) used by both the architect and builder branches so they can't drift. Persisted-config only (not CLI/env override-aware), matching the spec's scope. - getRetirement() is the single source of truth for "retired": its truthiness gates the branch and its message is surfaced. The architect branch now shows the retirement (both roles) instead of "builder-only"; a new builder branch flags a retired builder harness proactively (Phase 2 already fails it closed at spawn — this is user education before a spawn is rejected). - Update the structured issue:/recommendation: fields (the stable assertion target): "<harness> configured as {architect,builder} shell (harness retired)". Tests (doctor.test.ts, new '#1338 shell-harness retirement flagging' describe): gemini builder and gemini architect configs assert the structured fields and that no output claims "supported for builders"/"builder-only"; explicit builderHarness: gemini is detected; a supported config (claude builder + codex architect) is not flagged. Doctor never calls resolveHarness, so it detects and reports without throwing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…INI_SYSTEM_MD (iter2 review) The README custom-`gemini` escape-hatch injected the role via `--system`, but the Gemini CLI reads its system prompt from the GEMINI_SYSTEM_MD env var (retired GEMINI_HARNESS at e222b9e^: args:[] + env{GEMINI_SYSTEM_MD}, empty script fragment). A copy-paste user got a launch line the CLI rejects. Reproduce the retired provider verbatim (roleArgs:[] + roleEnv/roleScriptEnv) and realign the same-named "retained-access escape hatch" test to assert the GEMINI_SYSTEM_MD injection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… to name the explicit selector The three retirement-guidance touchpoints must agree so a retained-access user can actually follow the escape hatch. The README was fixed in phase_4; this aligns the runtime message and doctor recommendation (accepted + deferred to Review by all three reviewers across phase_4 iterations): - harness.ts RETIRED_HARNESSES.gemini: name the explicit shell.builderHarness / shell.architectHarness selector (a bare auto-detected `gemini` stays retired). - doctor.ts builder + architect recommendations: the "custom harness" advice now names the role-appropriate explicit selector. - CHANGELOG: point at the README's GEMINI_SYSTEM_MD snippet (Claude iter3 note). Tests: lock the message/recommendation shape (harness.test.ts, doctor.test.ts). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ni harness retirement - arch.md: drop gemini from the built-in HarnessProvider list; replace the stale "Gemini is builder-only" framing with current state (retired both roles, fails closed, custom-only via explicit selector); fix a now-stale override example. - lessons-learned.md: +2 durable lessons (fail-closed at every resolution path when retiring a registry entry; documented config snippets must reproduce the real mechanism). Historical #929 lesson left intact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Architect Integration Review — PR #1342Contributor-side integration review (we are not upstream maintainers; final merge is a maintainer's call). Ran a 3-way CMAP plus an independent line-by-line pass of the production diff (resolver, config/spawn preflight, all four architect boundaries, 3-way CMAP verdicts
The design itself is sound and consistently applied: both resolver footguns are closed (no silent Blocking (must address before merge)1. 2. Revert the Non-blocking (cheap to fold in while revising)
Follow-ups (not this PR)Doctor doesn't flag an unknown explicit harness name; Merge is held pending the two blocking items above, human validation, and an upstream maintainer's approval. Not self-merging. Architect integration review |
…ease changelog) The upstream release CHANGELOG.md is maintained by the maintainers, not contributors, so this PR should not add to it. The Spec 1338 retirement is documented via the README + the runtime/`doctor` retirement messaging instead. Reverts the drafted [Unreleased] "### Removed (Spec 1338)" block. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…onal spawn preflight)
The retirement preflight was gated `if (mode !== 'shell')`, but `spawnShell`
still runs `commands.builder` via `startShellSession` (a PTY) and persists a
shell row via `upsertBuilder`. A retired gemini `shell.builder` would therefore
launch the retired CLI and leave a shell row behind. Make the preflight
unconditional; `getBuilderHarness` still honors the custom-harness escape hatch,
so behavior is unchanged for every supported harness and every mode.
Regression test drives the real `spawn({ shell: true })` against a real temp
workspace and asserts it rejects with the retirement and leaves no PTY / row /
on-disk state — the guard against re-introducing the shell exemption.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…om-harness recommendation The custom-harness clause in the retirement recommendations hard-coded "gemini", while the console and structured-issue lines two lines up already interpolate `role.name`. Interpolate `architect.name` / `builder.name` so the advice stays correct if a second harness is ever retired (RETIRED_HARNESSES is extensible). Byte-identical output for the only current retired harness (gemini), so existing assertions still pass; new assertions lock the interpolated clause. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n pruning a sibling registration
`siblingRegistrationIsLive` returned `false` silently for a retired architect
harness, so the reconcile loop's prune log ("no live terminal and no resumable
session") misattributed why the row went away. Thread an optional `log` through
the opts (idiomatic — tower-utils uses injected loggers, not a module logger)
and emit the retirement-specific reason in the catch; the reconcile caller passes
its logger. Tests assert the line fires on retirement and does not fire for a
live (supported) sibling.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s + scrub CHANGELOG claims Review doc: a new Deviations bullet records the four integration-review changes (CHANGELOG revert, unconditional --shell preflight, doctor role.name interpolation, sibling-prune retirement log), and the stale CHANGELOG claims in the Summary / Spec Compliance / Consultation Feedback are scrubbed so nothing implies this PR edits the release changelog. Spec/plan Status draft -> approved. Thread updated with the full integration-review narrative. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rtifacts (audit trail) Codex PR review (iter1) asked for the five untracked `*-context.md` consultation context files to be committed, consistent with prior SPIR projects — 11 such files are already tracked across the repo (e.g. 0104-custom-session-manager), alongside the `*-rebuttals.md` files this project already commits. Adds the audit artifacts for the phase 2–4 re-iterations. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… interpolation-test comment
Two non-blocking Claude PR-review notes:
- `assertBuilderHarnessNotRetired` swallowed non-retirement resolution errors
silently. Worktree modes re-surface them at the real `getBuilderHarness` call,
but shell mode never resolves a harness downstream — so the now-unconditional
preflight is the one place such an error is seen. Add a `console.debug` so a
misconfigured `builderHarness` stays diagnosable; it fires only on an actual
error, so the normal supported-harness path is unaffected.
- The doctor `${role.name}` interpolation assertion reads identically under the
old hard-coded literal (only one `RETIRED_HARNESSES` entry), so its comment
overstated what it proves. Reworded (builder + architect) to say it locks the
rendered clause; the interpolation is verified by inspection and the already-
asserted console/issue lines.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ups + iter1 rebuttal Addresses Codex PR review (iter1): refreshes stale Key Metrics (commits 47 -> ~60, tests 4145 -> 4148), the Flaky-Tests count, and the Consultation-Summary counts (33/11/7 -> 36/12/8). Adds two Follow-up Items from Claude's non-blocking review (resolveHarness prototype-chain lookups; stale Gemini-CLI mentions in INSTALL.md and SKILL.md, pre-existing since #778). Adds the iteration-1 review rebuttal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Architect Integration Review — Update: all items resolved ✅Follow-up to my integration review above. Every blocking and recommended item is addressed, and I re-verified each against the diff (not just the builder's summary). Blocking — resolved
Recommended — folded in
Re-review on the fixed code (fresh 3-way)
State
From the contributing architect's side this is ready for maintainer review and merge. We are not upstream maintainers, so we are not self-merging — the GitHub merge is a cluesmith/codev maintainer's call. Architect integration review (update) |
Architect Integration ReviewExcellent work — this is a model retirement: fail-closed at every boundary with zero orphaned state, a principled escape hatch (explicit custom name wins; auto-detect never consults customs), and the I'd like to see one change before merge:
Optional, take or file as follow-ups (from the claude lane — all non-blocking):
The migration story (doctor → runtime message → README, all naming the same explicit selector) is coherent across all three surfaces, and thank you for the disciplined contributor-side CMAP — it made this review fast. |
…n restartOptions Upstream maintainer review (PR #1342, BLOCKING): both reconnect paths in tower-terminals.ts forced `ptySession.restartOnExit = true` for EVERY architect. A retired-harness architect resolves `restartOptions` to `undefined` (buildArchitectReconnectRestartOptions fails closed), so `reconnectSession` was told NOT to configure an auto-restart — SessionManager mirrors this with `restartOnExit: hasRestart` (session-manager.ts:577). Forcing the PTY flag true anyway made PtySession hold WebSocket clients in a "restarting…" wait (startRestartWait) for a process that can never come back. Gate both sites on `dbSession.type === 'architect' && restartOptions` — the startup reconcile path (restartOptions added to the probeResults destructure) and the on-the-fly getTerminalsForWorkspace path (restartOptions already in scope). The PTY flag now tracks whether a restart is actually configured. Test (consumer path, per the maintainer's explicit ask): new tower-terminals-restart-gating.test.ts mocks buildArchitectReconnectRestartOptions and drives BOTH real reconnect paths x {undefined -> false, defined -> true}. Mocking the helper isolates the consumer's gating branch; the helper's real undefined-return is separately covered in tower-utils.test.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… row instead of pruning it Upstream maintainer review (PR #1342, optional). siblingRegistrationIsLive returned `false` for a retired architect harness, which makes the reconcile loop DELETE the user's sibling config row (setArchitectByName null). Return `true` instead: it routes to addArchitect, which fails closed on the same retirement (tower-instances.ts:1094 — "no state created before this point") and LEAVES the row intact, so the registration survives for when the user repairs the harness (fixes it, or wires a custom "gemini"). Same launch safety, non-destructive to user config. Update the docstring + the diagnostic INFO (now explains the row is KEPT, not pruned). Adjust the two tower-utils.test.ts retirement tests (returns-false -> true; log "pruning" -> "keeping the row") and correct a now-stale "retired-harness prune" comment in tower-instances.test.ts (its reconcile tests mock the predicate, so the false->prune / true->respawn contract is unchanged). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ght log through logger.debug Upstream maintainer review (PR #1342, optional). assertBuilderHarnessNotRetired's catch logged a non-retirement resolution error via `console.debug`. Tower imports this module, and `console.debug` always writes to stdout — polluting Tower's log stream. Route it through `logger.debug` (agent-farm/utils/logger), which is DEBUG-gated, so the deferred-error line stays silent in Tower unless DEBUG is set while remaining diagnosable when needed. Strengthen the config.test.ts "defers on unknown harness" test to spy logger.debug and assert the deferred error is logged there. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…N_HARNESSES lookup Upstream maintainer review (PR #1342, optional). `BUILTIN_HARNESSES[name]` was indexed with a user-controlled key at two sites — resolveHarness (config shell.builderHarness / shell.architectHarness) and harnessProviderFor (a running builder's launch-script harness name). For an inherited Object key like "constructor" the bare index returns a truthy value (Object's constructor), which `if (builtin) return builtin` would hand back as a bogus provider that TypeErrors at the first buildRoleInjection call. Add getBuiltinHarness(name) — an own-property accessor mirroring the guard isRetiredHarness already gives RETIRED_HARNESSES — and use it at both sites. The constrained auto-detect lookup (resolveHarness:466, `detected` in {claude,codex,opencode}) stays a direct index; only user-controlled keys need the guard. Tests: resolveHarness rejects inherited Object keys with "Unknown harness" (never a bogus provider); a getBuiltinHarness describe covers real names, unknown names, and the proto-key footgun. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(1 blocking + 3 optional) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@waleedkadous thanks for the careful review. Pushed the blocking fix + all three optional items as four atomic commits ( BLOCKING — reconnect
|
Summary
Retires the standalone Gemini CLI as a supported built-in builder/architect harness. Google ended consumer Gemini CLI access (free, Pro, Ultra) on 2026-06-18, so it can no longer be presented as supported. A
geminiharness now fails closed at every spawn (including--shell) / architect launch / reconnect / clean-exit boundary with a specific retirement message (no orphaned worktree, no stray shell PTY or DB row, no Tower crash);codev doctorflags a persistedgeminiconfig for both roles; and the docs no longer present it as a built-in. Retained-access users (Standard/Enterprise or API-key) keep a sanctioned escape hatch: an explicit customgeminiharness selected viashell.builderHarness/shell.architectHarness.Closes #1338
Changes
RETIRED_HARNESSESsentinel in the sharedresolveHarnessfails closed on both the explicit path (specific retirement, not a generic "unknown" error) and the auto-detect path (never the silentCLAUDE_HARNESSfallback). Built-inGEMINI_HARNESSremoved;geminiretained in the detector so ageminicommand is recognized-and-retired, not misclassified.--shell(before any worktree / porch / PTY / DB state is created), architect launch, reconnect (buildArchitectReconnectRestartOptions→ fail closed), and clean-exit relaunch (a newFreshLaunch { stop: true }contract). No orphaned state, no Tower crash.geminibuilder/architect config with the retirement message, for both roles; preserves the custom-harness escape hatch (an explicit<role>Harnessbacked by a matching custom def is not flagged). The custom-harness recommendation interpolates the configuredrole.namerather than a hard-coded"gemini", so the advice stays correct asRETIRED_HARNESSESgrows.GEMINI_SYSTEM_MDenv injection); the runtime message, doctor recommendation, and README all name the explicit selector; governance docs (arch.md, lessons-learned.md) refreshed. The upstream releaseCHANGELOG.mdis intentionally left untouched — contributors don't edit it.siblingRegistrationIsLivelogs the retirement reason when it prunes a retired-architect registration, so the reconcile loop's generic "no resumable session" prune line can't misattribute the cause.geminiconsult lane (nowagy).Testing
spawn-retirement.test.tsdrives the realspawn()against a real temp workspace and asserts 0 orphaned.builders/+ 0 porch state after a rejectedgeminispawn — for both a worktree spawn and a--shellspawn (shell mode is no longer exempt from the preflight).tower-utilsasserts the retired-prune log fires (and does not fire for a live sibling).Spec
codev/specs/1338-retire-gemini-cli-as-a-builder.md
Review
codev/reviews/1338-retire-gemini-cli-as-a-builder.md
🤖 Generated with Claude Code