Repository navigation
Let agents name their own tab, with a user-set name winning for good - #340
Merged
Merged
Conversation
Adds a `set_tab_name` MCP tool so a chat or terminal agent can rename the tab it's running in. The tool takes only a name — the target tab comes from the caller's scope, so there's no way to rename anyone else's. Provenance is tracked with a single optional field, `customLabelSource: 'agent'`, present only on agent-set names. The polarity is what makes this free: a `customLabel` with no marker means the user typed it, so every label already persisted on disk reads as user-set and locks agents out — the correct default with no migration code. - `source: 'user'` always wins and drops the marker, marking the name final - `source: 'user'` with an empty label (the existing "Reset Name") clears both fields, which deliberately re-opens agent naming — there'd be no other way to unlock it - `source: 'agent'` is refused outright once a user-set name exists, and `PanesFSM.renameTab` now returns whether it applied so the tool result can tell the agent to stop trying Agent-supplied names go through a new `normalizeTabLabel` (trim + clamp to 32 chars, null for empty). It's applied only on the agent path — clamping the user's typed input would silently change long-standing behavior. Two adjacent fixes that this feature made worse rather than merely inconsistent: `tabTypeChanged` rebuilt the tab from scratch and dropped `customLabel` on agent↔chat conversion, which would have also handed naming rights back to the agent; and the mobile tab strip rendered `tab.label`, ignoring custom names entirely. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The original wording said to set the name "once you actually know what this session is about", which is a judgment call an agent can defer indefinitely — the likely outcome being that the tool never gets called and every tab stays "Claude". Make it an explicit first-turn instruction instead, from whatever context is available, with permission to correct it later if the kickoff prompt turned out to be vague. Split the don't-churn guidance into its own paragraph so it reads as "then leave it alone" rather than hedging the first-turn rule. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Gates the set_tab_name tool behind `agentTabNamingEnabled`, defaulting to enabled. Follows `browserToolsEnabled` rather than the experimental opt-ins: only an explicit `false` is persisted, so absence means on and no migration is needed. Disabling hides the feature rather than rejecting it, matching the conversation-fork precedent — being told about a tool whose only outcome is a rejection wastes an agent's turn: - the bridge filters `set_tab_name` out of tools/list - `buildClaudeLaunchSettings` strips the prompt section, which is now extracted as HARNESS_SYSTEM_PROMPT_TAB_NAMING_SECTION - the route still 403s as a backstop for a stale bridge The setting lives under Agents → General, not Experimental: it's agent-agnostic, default-on, and gates a cosmetic reversible action. The card is explicit that it governs agents only — the user's own rename is never gated — because "turn this off to protect my tab name" is the plausible misreading, and the per-tab lock already covers that. A test pins that stripping the section leaves the surrounding bullet list intact, since it's the tail of a list and a sloppy strip would leave a dangling blank line. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a
set_tab_nameness-control MCP tool so an agent can rename the tab it's running in, and makes a name the user typed by hand permanently final.The tool
set_tab_nametakes only aname. There is notabIdorworktreePathparameter — the target is resolved from the caller's scope (x-harness-terminal-id→resolveCallerScope), so an agent can only ever rename itself. Backed by a newPOST /tab-nameroute on the control server, following the tab-scoped shape of/shells(400 without the header, 404 when it resolves to nothing).Provenance, in one optional field
TerminalTabgainscustomLabelSource?: 'agent', present only on agent-set names. That polarity is what makes the migration free: acustomLabelwith no marker means the user typed it, so every label already persisted on disk reads as user-set and locks agents out — which is the behavior we want, with zero migration code.terminals/tabRenamednow carriessource: 'user' | 'agent':usercustomLabel, drops the marker — always wins, name is now finaluseragentPanesFSM.renameTabnow returns whether the rename applied, so the tool result can tell the agent its rename was refused and to stop retrying. It mirrors the reducer's lock rule explicitly rather than inferring refusal from a no-op dispatch — otherwise an idempotent re-set of the same name would read as a refusal.Agent names go through a new
normalizeTabLabel(trim, clamp to 32 chars,nullfor empty), applied only on the agent path. Clamping the user's typed input would silently change long-standing rename behavior.The injected system prompt gains the tool and a paragraph on keeping the name current: set it once you know what the session is about, Title Case and a few words, re-set on a genuine change of focus, don't churn it, and a user-set name is final.
The setting
agentTabNamingEnabled, default on, under Agents → General. It followsbrowserToolsEnabledrather than the experimental opt-ins: only an explicitfalseis persisted, so absence means enabled and there's no migration.Disabling hides the feature rather than rejecting it, matching the conversation-fork precedent — telling an agent about a tool whose only outcome is a rejection wastes a turn. Three layers:
set_tab_nameout oftools/listbuildClaudeLaunchSettingsstrips the prompt guidance, now extracted asHARNESS_SYSTEM_PROMPT_TAB_NAMING_SECTIONPOST /tab-namestill 403s, as a backstop for a bridge started before the toggle flippedIt's deliberately not in Experimental: it's agent-agnostic, default-on, and gates a cosmetic reversible action. The card says explicitly that it governs agents only and that your own rename always works — "turn this off to protect my tab name" is the plausible misreading, and the per-tab lock already covers that case.
One test pins that stripping the section leaves the surrounding bullet list intact. The section is the tail of a list, so it's interpolated after a single
\nand removed with one; getting that wrong leaves a dangling blank line in the prompt.Two adjacent fixes
Both were pre-existing, but this feature turned them from "inconsistent" into "actively wrong":
tabTypeChangeddroppedcustomLabelon agent↔chat conversion, because both branches rebuild the tab from scratch. With provenance in play that didn't just lose the name — it also handed naming rights back to the agent, so a user-set name would appear to have been overridden. Now carried across with its marker.tab.label, ignoring custom names entirely.Not touched:
MobileTerminal.tsx'ssessionName={tab.label}looks like a third instance of the same bug but isn't — that value feeds the agent spawn args, not the display.Verification
npm run typecheck,npx electron-vite build, andnpx vitest run(1999 tests) all clean. New tests cover every reducer path, the FSM return value, and the route (applies / refused / clamps / 400 / 404).The
mcp-bridge.jsJSON-RPC surface was also exercised directly against a stub control server, since it has no vitest coverage:tools/listadvertisesset_tab_namewith a valid schema, andtools/callreturns the right text for a plain rename, a clamped over-long name, a refusal, and a rejected empty name. Re-run with the gate off, the tool drops out oftools/list(27 → 26 tools) and the call surfaces the 403 — so both layers of the setting hold.Two caveats worth stating plainly. I have not exercised the tool inside a real Ness session — that needs the app rebuilt and restarted, which I can't do from inside a session the restart would kill, so the unverified link is the live app wiring (
panesFSM.renameTabreached through the real control server), not the tool or route logic. And I have not seen the new Settings card rendered; it typechecks and builds, and itsHotkeyBadge/resolvedHotkeys.renameTabreferences resolve, but nobody has looked at it.I also saw a single test failure in one full-suite run that did not reproduce across four subsequent runs and that I could not attribute to these files — the suite emits
git cherry-pickconflict noise in passing runs too, so a git-touching test is the likely source. Flagging it rather than claiming the suite is deterministic.🤖 Generated with Claude Code