feat(coder,desktop): coding agents get a generated title, renamed from the tab's right-click menu
Summary
Dean: "I like the chat title plan. I don't want double-click on the tab to rename, I want a right-click menu just like in the plans on the left side bar. Let's not do any retitling though." And: "retitling just seems like added complexity that we want to avoid. Do this in the cleanest way possible." So: one generation at start, a rename, nothing else. No retitle hook and no generated-vs-renamed flag: the generated title is written only where no title stands, which is what keeps a rename from ever being covered.
- Storage:
coding_agent.title, nullable, in migration0069_coding_agent_title(renumbered as #1954, #1958, and #1965 took0066ā0068) andcoding-agent-schema.ts. Null is "untitled", and the tab falls back to the model's name. - Generation, once: when the agent's first eve session starts (
coder/hooks/store.ts, onsession.started), the coder host generates a 3ā6 word sentence-case title from the plan's name and the agent's first message (the plan text, or the developer's task since #1958), and writes it where the row's title is still null (fillCodingAgentTitle). A later session of the same conversation (a restart) generates nothing. It runs on a detached fiber beside the turn, so neither the Start request nor the turn waits for it. One call onclaude-haiku-5-5through the same@ai-sdk/anthropicdoor the steps use, with a fixed short prompt, 64 output tokens, low effort, and a 20 s bound; quotes and a trailing full stop are stripped. Any failure leaves the title null. Without an Anthropic key, agents stay untitled. - Rename:
PATCH /api/agents/{id}takes{ title }beside the existing{ model, effort }. Owner-only, trimmed, bounded at 80 characters; an empty title clears it. The answer is the agent's summary, which now carriestitle. - Wire:
codingAgentSummarySchema.title(null or string) for the voice-control PR to read, and the transcript page carriestitletoo, because the list that opened the tab was read before the generated title landed; the tab's held read brings it in a moment later. - Desktop:
- The agent tab's label is the title, or the model's name until it has one; the model is in the tab's hint.
- Right-click on an agent tab opens the same
ActionMenuthe sidebar plan rows use, where the pointer is, with Rename. Rename turns the tab into the same inline name field the plan tab uses (PlanNameField, now taking its label and bound), and Return renames through a newcodingAgents.renameact ā gatewaycodingAgents.renameāPATCH. No double-click rename. - Notifications say the title where there is one ("Sign-out button in account menu finished").
- Left out, on purpose: the PR chip's Open pull request and Copy branch name are not in the tab menu. The published read lives with the shown tab (
useAgentPullRequest), so offering them from the strip would mean a GitHub read on right-click for every tab. The menu is Rename alone.
Evidence
- Platform-independent checks:
./scripts/check.shexits 0. - macOS Electron verification (
./scripts/verify.sh):not run. CI could not verify this UI change. CI builds nothing for the Mac and this was done on a Linux VM; Dean waivedverify.shfor this rollout. LUKE-159 is the manual Mac pass.
Visual evidence
Headless Chrome render of the real DesktopPlans page and side panel (stub controls, the renderer's built stylesheet). Three agents: two titled, one still on its model's name. Second row: right-click on the first agent's tab.
| Dark | Light |
|---|---|
![]() | ![]() |
![]() | ![]() |
Physical-device evidence
- Screenshot or screen recording:
not attached(no Mac in this workspace)
Tests
Each fails on main and passes here:
coder-host.test.ts: the title is generated from the plan as the first session starts, tidied, and written once; a restarted session generates nothing; a rename is never covered by a generated title. A failing title call leaves the agent untitled.coding-agents-app.test.ts:PATCH { title }sticks, the list reads it back, an empty title clears it, another account's agent is not found, and a title past the bound is refused.desktop-plans.test.ts: agent tabs are named by title or else by model with the model in the hint; a right-click offers Rename where the pointer is, Rename turns the tab into a field, and Return renames the agent and hands focus back.use-coding-agents.test.ts: a rename asks the service for that agent and takes its answer; a refusal changes nothing; a transcript page's title lands on its agent.use-agent-transcript.test.ts,agent-notices.test.ts(the notification says the title, including one a transcript page carried just before the end),coding-agent-client.test.ts,compose-coding-agents.test.ts: the rename and the page's title through each layer.- After Devin's review: only the newest rename's answer stands when two answer out of order (as
onChoosealready did), and a transcript page's title reaches main's notice ledger. Left as is, with the reasoning on the threads: an emptied field renames nothing (the plan field's rule; clearing isPATCH { title: "" }), and a refused rename leaves the tab on its standing name with no note, since the strip has no note surface.
Test count: +6.
Invariants
-
./scripts/check.shgreen;verify.shnot run (Linux VM; see above). - JSON Schema goldens regenerated under
LUKE_UPDATE_FIXTURES=1:titleon the summary and the messages page, and the two new rename schemas. - Every behavior change has, in this PR, the test that fails without it; no change-detector test added.
- No new
astype assertion outside the allowlisted files. - No
Effect.runPromise/runSync/runForkoutside the runtime edges. - No new
setTimeout/setInterval/new Promise/AbortController/fs.watch. - No Effect, Stream, or Layer written as a statement and dropped.
- No
CLAUDE.md/AGENTS.mdsentence names a changed part.
š¤ Generated with Claude Code



