Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches: [main]
pull_request:
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- OpenCode falls back to readable local paths for unsupported attachment formats instead of sending provider-rejected file parts, and repairs sessions already stuck on an unsupported file turn. Fixes #211.

## [0.1.45] - 2026-09-13

### Added
Expand Down
40 changes: 40 additions & 0 deletions docs/worktree-naming-failures-research.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# AI naming failures: T3 Code comparison

Reviewed 2026-09-14 against official T3 Code main, commit `77bca8b2d76a1f42552e5eee7d277fcb1160347a`. The official GitHub repository was opened and `git ls-remote origin refs/heads/main` confirmed that the existing research checkout still matches main. This is a source investigation; no T3 provider quota was exhausted or live generation run.

## What T3 does

T3 treats first-message branch naming and thread-title generation as separate background operations. Both are forked before the main provider turn proceeds. A failed metadata request does not block worktree creation or fail the main conversation. [First-turn orchestration](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts#L1404-L1433)

| Behavior | Automatic branch name | Automatic thread title |
| --- | --- | --- |
| Automatic retries | None in the naming path. | Two additional attempts with exponential backoff starting at two seconds (two and four seconds). The retry does not classify quota errors separately. |
| Model failover after request failure | None. | None. |
| Final failure | Logs a warning; leaves the temporary branch intact if generation failed before rename. | Logs a warning; leaves the seeded/current title intact. |
| User-visible naming error | No error event or toast dispatched from this background path. | No error event or toast dispatched from this background path. |

These differences are explicit in the adjacent [branch and title helpers](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts#L991-L1106). A focused test exercises a transient title-generation timeout followed by a successful retry. [Retry test](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts#L1583-L1645)

There is a manual **Regenerate title** action. The client shows an error if submitting that command fails, but the background generation worker catches generation failures, logs them, and clears the pending regeneration without changing the title. This is not a branch-naming retry action or a quota-failover UI. [Client action](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/apps/web/src/components/Sidebar.tsx#L4390-L4408), [regeneration worker](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts#L1207-L1257)

## Model selection and quota

The model for branch names is the configured source-control writer selection, or the configured text-generation selection when no writer override is set. An unavailable/disabled writer instance falls back to text generation **before** requesting a name. The runtime service then resolves that single instance and calls it directly; it does not try another provider after a quota or inference error. [Selection helper](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/packages/shared/src/serverSettings.ts#L84-L100), [single-instance routing](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/apps/server/src/textGeneration/TextGeneration.ts#L124-L171)

Both selections have settings UI. Text generation defaults to Codex `gpt-5.6-luna` with low reasoning; per-provider defaults include Claude Haiku 4.5 and Cursor Composer 2. These are source defaults, not guarantees that a user's provider supports or has quota for that model. [Defaults](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/packages/contracts/src/model.ts#L164-L188), [text-generation setting](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/apps/web/src/components/settings/SettingsPanels.tsx#L2918-L2944), [writer setting](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/apps/web/src/components/settings/SourceControlWritingSettings.tsx#L283-L353)

Codex, Claude, Cursor, Grok, and Antigravity text-generation runners have 180-second timeouts. OpenCode's text-generation wrapper has no equivalent explicit timeout in that file, so a universal three-minute guarantee should not be inferred. Provider CLIs or services may implement their own retries; that is separate from T3's naming orchestration. [Codex](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/apps/server/src/textGeneration/CodexTextGeneration.ts#L41), [Claude](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/apps/server/src/textGeneration/ClaudeTextGeneration.ts#L53), [Cursor](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/apps/server/src/textGeneration/CursorTextGeneration.ts#L30), [Grok](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/apps/server/src/textGeneration/GrokTextGeneration.ts#L35), [Antigravity](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/apps/server/src/textGeneration/AntigravityTextGeneration.ts#L36), [OpenCode](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/apps/server/src/textGeneration/OpenCodeTextGeneration.ts)

## Why plain billing errors do not become T3 names

T3 asks for a JSON object and validates the expected field (`branch: string` or `title: string`) before sanitizing it. Cursor extracts a JSON object from the response and schema-decodes it; empty or invalid output produces a typed `TextGenerationError`. It has no plain-text naming fallback. Therefore a plain response such as “Upgrade your plan to continue” fails parsing rather than becoming a branch or title. [Prompt/schema](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/apps/server/src/textGeneration/TextGenerationPrompts.ts#L185-L207), [Cursor validation](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/apps/server/src/textGeneration/CursorTextGeneration.ts#L107-L168)

Codex additionally passes an output schema to the CLI, checks the process exit code, and schema-decodes its output file. Claude checks the exit code and validates the `structured_output` envelope. OpenCode checks `result.data.info.error` before reading text and validates its JSON. [Codex runner](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/apps/server/src/textGeneration/CodexTextGeneration.ts#L207-L312), [Claude runner](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/apps/server/src/textGeneration/ClaudeTextGeneration.ts#L248-L312), [OpenCode validation](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/apps/server/src/textGeneration/OpenCodeTextGeneration.ts#L246-L375)

Schema validation is structural, not semantic: T3 does not reject an otherwise schema-valid object merely because its `branch` or `title` contains billing-error words. That limitation follows from the string-only schema and subsequent sanitizer. [Schema](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/apps/server/src/textGeneration/TextGenerationPrompts.ts#L201-L207), [Cursor sanitation](https://github.com/pingdotgg/t3code/blob/77bca8b2d76a1f42552e5eee7d277fcb1160347a/apps/server/src/textGeneration/CursorTextGeneration.ts#L218-L239)

## Implications for Monocode

The useful T3 patterns are structured-result validation, preserved fallback names, independent background generation, and bounded retries (currently implemented for T3 titles). Monocode's combined title/branch request can reuse the retry policy while retaining its own cancellation, deadline, and native rename eligibility checks. Validate provider success/error metadata and structured fields before changing either name; arbitrary response prose must not be treated as a title or branch.

A visible **AI naming unavailable—kept the default branch name** notice with manual retry is an explicit Monocode improvement requested in this conversation, not behavior already present in T3's branch path. Quota/account failures should remain understandable and should not silently switch to another provider/account. Avoid describing pre-request provider selection fallback as runtime quota failover.
Loading
Loading