Skip to content

feat(session): show the live session name in the Status tab - #445

Open
ayush5harma wants to merge 9 commits into
srothgan:mainfrom
ayush5harma:contrib/session-title-sdk
Open

ayush5harma wants to merge 9 commits into
srothgan:mainfrom
ayush5harma:contrib/session-title-sdk

Conversation

@ayush5harma

@ayush5harma ayush5harma commented Oct 10, 2026 •

Copy link
Copy Markdown

Summary

This is the foundation for showing the session name. It gets the name into the app through the SDK's public session API and shows it in the Status tab. Where else to show it is left for you to decide (see Notes).

  • Source of the title. The bridge reads the title only through getSessionInfo(sessionId, { dir }).customTitle and sends a new session_title_update session update. The type is added in agent-sdk/src/types.ts and src/agent/types.rs together.
  • When it reads. After a connect, a session replacement, a conversation_reset and every top-level turn result. Also after the Status tab's existing rename_session and generate_session_title succeed; both keep using renameSession() and the SDK's title generation as on main. A read that a later read overtakes is dropped, and no other bridge state is kept.
  • Rust.
    • session_runtime.session_title is the one copy of the title. It is cleared with the session identity and on a conversation reset, as SDKConversationResetMessage asks. A repeated title is stored idempotently.
    • Control characters, C1 and DEL included, are stripped once in the converter.
  • UI. The Status tab shows the name. Its rename draft (r) and title generation (g) start from it, so both use the live name instead of the session list's snapshot.
  • Docs. A Session Name section in commands.md, and a session-title owner table in architecture.md, which separates the live title from the session list's snapshot.

Why

On main, a /rename typed in the composer, or a name given at launch, never reaches the Status tab. The tab shows the session list's snapshot, which nothing refreshes after a typed rename. This is the session-name half of #438; the reply half is #444.

#442 tried this with undeclared stream fields, raw transcript records and --name. This version uses only the session API, following your review on #443.

Refs #438

Validation

  • Automated, on this branch (macOS 27.2, Apple Silicon, rustc 1.98.1 from nixpkgs rather than the CI toolchain):
    • cargo fmt --all -- --check, cargo fetch --locked: pass

    • cargo clippy --locked --all-targets --all-features -- -D warnings: pass, with the same five clippy 1.98 lints allowed that main fails (collapsible_match, manual_is_multiple_of, manual_is_variant_and, map_unwrap_or, while_let_loop)

    • cargo test --locked --all-features: pass. The serial terminal_resize run: pass.

    • agent-sdk: npm run build, test, lint, knip and audit: pass. quality:duplicates finds no clone in a file this PR touches.

    • The bridge test spawns the bridge with a stubbed SDK (getSessionInfo, renameSession, query). It asserts the NDJSON for:

      • resume;
      • a title that appears after the first turn;
      • /rename;
      • a /clear replacement;
      • a conversation reset;
      • a Status-tab rename and title generation;
      • a dropped stale read;
      • a failing read.

      Removing the stale-read guard makes the test fail.

    • The Rust tests cover the converter and reset handling. A PTY test renames through the fake bridge and reads the Status row.

  • Manual: I drove the debug binary in tmux against the Claude Code bundled with the pinned SDK (2.1.288). After /rename status-probe-title3, then /config and Tab to Status, the tab read Session name: status-probe-title3. Nothing refreshed the session list after that typed rename, so the value came from the live title.
  • Screenshot/video (if UI changed): the screen text above. The Status tab's existing name row now shows the live title.

Notes

  • Breaking changes: N/A
  • Docs updated: docs/src/commands.md, docs/src/architecture.md
  • No SDK upgrade: everything here is in the pinned @anthropic-ai/claude-agent-sdk 0.3.288. The read points come from behaviour measured on the Claude Code 2.1.288 it bundles: a launch-time name and a /rename reach getSessionInfo only once the turn has finished, and right after init it reports nothing.
  • customTitle falls back to a generated title for a session nobody named, although its doc comment describes the /rename title. Such a session therefore shows that generated title after its first turn. No public field tells the two apart, and the docs state this as the API's behaviour.
  • Like main, the resume picker's list is not refreshed after a /rename typed in the composer, only on connect, replacement and the Status-tab actions. I left that as it is rather than add state for it.
  • Earlier commits on this branch added a composer rule and a tab-title change; the later commits remove both, so the net diff touches neither. If you squash, the PR title describes the result.
  • Governance/release impact: N/A

- add the session_title_update session update to types.ts and the Rust
  wire types, read from getSessionInfo().customTitle after every connect,
  replacement and conversation reset, and after each top-level turn when
  it changed
- drop a read that a later read, a replacement or a close superseded, and
  refresh the session list when a turn changed the title
- strip control characters once in the converter, hold the active
  session's title as one Option cleared with its identity and on a
  conversation reset, and name the terminal tab after it
- cover the read rules, resume, a launch-time name, /rename, /clear and a
  conversation reset at the bridge boundary, and the converter and reset
  handling in Rust tests
- draw a `──── name ─` rule as the editor's top row when the session has a
  title, cut with an ellipsis to the width, and give it up first on a
  one-row slot
- name the session in the Status tab by the title Claude Code reports
  instead of the listed custom title, keeping the summary fallback
- cover the rule's layout and the Status row in unit tests, and /rename and
  a Status-tab rename against the fake bridge in PTY tests
- read the title back through getSessionInfo() after rename_session and
  generate_session_title succeed, so the composer rule, tab title and
  Status tab follow a rename made outside a turn
- keep both commands on renameSession() and the SDK's title generation;
  the session list is still emitted as before
- cover a rename and a generated ai-title at the command boundary against
  transcripts read through the SDK
…ve title

- start the rename draft from the title the bridge reported instead of
  the listed session's custom title, which can lag behind it
- describe the session for title generation by that title first, then
  the listed summary and first prompt as before
- the Status tab, the composer rule and both actions now read one title;
  the config tests and the Status-tab PTY test cover the prefill
- add a Session Name section: /rename and the Status tab change it, the
  app shows what the session API reports, including after a reset
- state that Claude Code generates a title only for a session without
  one, and that the API's generated-title fallback shows a name where
  Claude Code's prompt bar shows none
- add a session title owner table: Claude Code persists it, the bridge
  reads and sends it, Rust holds one value that every display reads
- remove the composer rule and its layout hookup; no display placement was agreed before it was added
- restore upstream's terminal tab title so it keeps naming the folder
- keep one PTY test proving the Status tab shows the name a /rename reports
- drop the fake bridge's resume title and rename handler that only the removed tests used
…ecords

- have the SDK stand-in report titles from its own state through getSessionInfo, so no test writes CLI-private custom-title or ai-title records
- drive every case through the spawned bridge and assert the NDJSON it emits: resume, a launch-time name after the first turn, /rename, the Status rename and title generation, a conversation reset and /clear
- prove a superseded read is dropped with a held read that resolves after a later one starts
- prove a failed read is logged and sends nothing
- merge session_resume.test.ts into session_title.test.ts, since both tested the same title workflow
- remove the bridge's sentTitle copy, which mirrored the title Rust already holds
- send whatever each read returns; Rust stores it idempotently and nothing else reacts to a repeat
- keep the titleReads counter so a superseded read is still dropped
- remove the per-turn session-list refresh that depended on change detection
- cite Claude Code 2.1.288, the version bundled with the pinned SDK
- say where the name shows (the Status tab), when it is read, and that it is whatever the SDK session API reports
- state the generated-title fallback as a property of the API and drop claims about how Claude Code keeps titles
- name the SDK as the title's owner and the listing as a separate snapshot refreshed by listSessions
- reword the Rust field comments the same way
@ayush5harma ayush5harma changed the title feat(session): show the session name from Claude Code's session API feat(session): show the live session name in the Status tab Oct 10, 2026
@ayush5harma

Copy link
Copy Markdown
Author

Hi @srothgan, this is ready for review whenever you have time.

Before opening it I read CONTRIBUTING.md, AGENTS.md and docs/src/architecture.md, and applied your feedback from #443 and #441:

  • The title comes only from the SDK's public getSessionInfo().customTitle. Nothing reads Claude Code's transcript records, undeclared stream fields or its UI, and the tests stub the SDK rather than write its private file format.
  • There is one owner per side: the bridge reads and sends the title, and Rust holds it as a single value. The architecture doc has an owner table for it, separate from the session list's snapshot.
  • The wire type changed in types.ts and types.rs together. CHANGELOG is left to you, and there is no agent attribution in the commits.
  • There is no SDK upgrade. Everything uses the pinned @anthropic-ai/claude-agent-sdk 0.3.288, and I measured the behaviour on the Claude Code 2.1.288 it bundles.

After a self-review against those rules, I cut this PR down to the foundation:

  • The bridge sends the title, and the Status tab shows the live name.
  • I dropped the rule above the composer and the tab-title change I had first added, so that nothing imitates Claude Code's interface.
  • The net diff is now +522/-27, mostly tests.

One question for you: would you like the session name shown anywhere else, such as the footer or the terminal tab title? I'd rather follow your call on placement than guess. I can do that in a follow-up.

I couldn't apply labels. Per CONTRIBUTING, I think type: feature, area: agent-sdk and area: tui fit.

Could you check it on your end and merge it if it looks good? Happy to change anything you'd like done differently.

ayush5harma added a commit to ayush5harma/claude-code-rust that referenced this pull request Oct 10, 2026
…s-prs

chore: make the fork exactly upstream plus PRs srothgan#444 and srothgan#445

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant