Skip to content
This repository was archived by the owner on Oct 10, 2026. It is now read-only.
This repository was archived by the owner on Oct 10, 2026. It is now read-only.

Thread title regeneration silently no-ops for Amp/Copilot/Droid/Gemini text-generation instances #197

Description

@aaditagrawal

Follow-up from the 2026-07-30 upstream sync stack (#190–#196). Surfaced by the Codex reviewer on #195 and confirmed by triage.

Problem

Upstream's thread title regeneration (pingdotgg#4810) assumes every selectable text-generation instance can produce a title. That holds for all five upstream drivers — Codex, Claude, Cursor, Grok and OpenCode all implement generateThreadTitle for real.

This fork ships four instances that structurally cannot:

  • apps/server/src/textGeneration/AmpTextGeneration.ts
  • apps/server/src/textGeneration/CopilotTextGeneration.ts
  • apps/server/src/textGeneration/GeminiCliTextGeneration.ts
  • apps/server/src/provider/Drivers/DroidDriver.ts (inline stub — no separate module)

They unconditionally Effect.fail(new TextGenerationError(...)).

regenerateThreadTitle resolves the generator from the global serverSettingsService.getSettings.textGenerationModelSelection, not the thread's provider. The Effect.catchCause in ProviderCommandReactor.ts then converts that failure into a successful completion with no title. Result: the user picks "Regenerate title", the spinner appears and clears, and the title is unchanged with no error.

Nothing gates the action — capabilities.threadTitleRegeneration is hardcoded true, the sidebar menu keys off that flag alone, and the settings UI offers every registered instance as the text-generation instance with no capability filter. There is no supportsTextGeneration concept in the repo.

Why it wasn't fixed in the sync stack

The obvious cheap fix — deriving capabilities.threadTitleRegeneration from whether the configured instance supports text generation — does not work. The descriptor is built once at startup in ServerEnvironment.ts (getDescriptor: Effect.succeed(descriptor)), while the text-generation instance is a runtime setting, so the flag would be stale as soon as the user changes it.

Suggested fix

Carry the failure through instead of swallowing it:

  1. Add an optional error field to ThreadTitleRegenerationCompleteCommand in packages/contracts/src/orchestration.ts and its projected event.
  2. In ProviderCommandReactor.ts, pass TextGenerationError.detail into dispatchThreadTitleRegenerationCompletion rather than dropping it.
  3. Project it onto thread.titleRegeneration and surface it as a toast in SidebarV2.

This also fixes the general case: the same catchCause swallows transient CLI and network failures on upstream too. The fork's stub drivers merely make it fail 100% of the time, which is why it became visible here. Worth reporting upstream as well.

Compare GitManager.ts, where generateCommitMessage failures propagate as typed errors to the RPC caller and a stub driver produces a visible error — title regeneration is the only text-gen path that silently completes.

Activity

  1. aaditagrawal commented on Aug 1, 2026

    @aaditagrawal
    OwnerAuthor

    Fixed and merged. Summary of what shipped, since the final shape differs from the suggestion in the issue.

    What landed

    #198 — carry the failure through. ProviderCommandReactor's catchCause turned a generation failure into a successful completion with no title; it now carries TextGenerationError.detail into thread.title.regeneration.complete. As the issue predicted, this fixes the general case too — the same path was swallowing transient CLI and network failures for the drivers that do implement generation, not just the four stub instances.

    #199 — repair migration id reuse. Follow-up to a defect introduced during #198's own review; details below.

    Where it diverged from the suggested fix

    The issue proposed projecting the error onto thread.titleRegeneration. That was implemented first and turned out to break older clients: they read "regeneration in flight" as titleRegeneration != null, so a failure recorded there leaves them showing "Regenerating…" with the action disabled forever. The failure now lives on a sibling titleRegenerationFailure field, and titleRegeneration stays strictly pending-only — an old client sees a failure exactly as it sees a success (cleared) and ignores the field it cannot read.

    A transient event-only signal was also considered and rejected: the sidebar is fed by the shell stream, which coalesces to the latest event per aggregate and re-reads projected state, so a one-shot notice would be dropped under load. The failure has to be state to reach the sidebar reliably.

    On the UI: the toast fires only on a live transition, so reloading never replays old errors. Because that leaves the reason invisible after a reload, the context-menu item also carries it — "Retry regenerate title — Amp does not expose a structured text-generation API".

    Not addressed

    Gating the action up front, for exactly the reason you gave: the descriptor is built once at startup while the text-generation instance is a runtime setting. Filtering the settings picker by a static per-provider-kind supportsTextGeneration capability would stop an unsupported instance being selected at all, but that is a new capability concept and wants its own change. Worth a separate issue.

    The upstream report you suggested has not been filed.

    Two things worth flagging

    The vp binary on PATH is broken. It fails at collection with TypeError: Cannot read properties of undefined (reading 'config') on every file, including a two-line smoke test. I initially concluded the suite was unrunnable and said so in #198. That was wrong — npx vp test run resolves a different version and works fine. Everything is green now (2613 tests in apps/server + packages, 1724 in apps/web), but #198 was merged without its tests having been executed.

    #198 left ProjectionSnapshotQuery.test.ts red on main — adding the field to the hydration query left the expected objects one key short. Fixed in #199, and it would have been caught pre-merge if the runner had been working.

    Release

    Tagged v0.38.0 (222 commits since v0.37.0, covering the #190–#196 sync stack plus these fixes). The release has not built. Every Release workflow run on this fork sits queued indefinitely — the workflow targets blacksmith-* runners that this fork does not appear to have. v0.37.0 has the same problem: tagged 2026-07-25, never released. The newest published release is still v0.36.0.

    So the tag is in place and the run is queued, but no artifacts will be produced until the runner situation is resolved. That needs a decision — either provision the runners or switch the workflow to ubuntu-latest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions