Skip to content

Rename a loop when its session is renamed (#261) - #263

Open
scgopi wants to merge 1 commit into
mainfrom
feat/261-session-rename
Open

Rename a loop when its session is renamed (#261)#263
scgopi wants to merge 1 commit into
mainfrom
feat/261-session-rename

Conversation

@scgopi

@scgopi scgopi commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Fixes #261.

Renaming a session from inside it (/rename hello) left the loop's card showing whatever title it was created with. The title is the one field a loop is expected to outgrow — it is written before the work exists — so the two names disagreed at exactly the moment the human had just said what the loop is.

Where the answer comes from

Claude Code writes it down, in the transcript GraphCode already knows how to find:

{"type":"custom-title","customTitle":"hello","sessionId":"a825abc7-…"}

It is a standalone record, re-emitted at every checkpoint rather than once at the rename, so a tail read always carries the current one and the last one wins. Read on the existing 15s presence poll, applied through the same renameNode a human's rename goes through — so a blank title is refused and everything a rename deliberately leaves alone (id, session, edges, layout) stays left alone.

Three things this is careful about

custom-title only. Every session also carries an agent-name record, and it is not the same thing: it holds a name the CLI assigned itself (angleReuse2) and is present in sessions nobody has renamed. Reading it would retitle most of a graph to strings no human ever typed — a far worse bug than the one being fixed. anAgentNameIsNotARename pins this.

Once per change, not once per sight. LoopNode.sessionTitle records what the session was last observed to be called, which is not what the node is called — the two part company the moment somebody renames the card from the sidebar, and that gap is what keeps the card's name theirs instead of having it overwritten on the next tick. It is persisted for the same reason: a daemon restart must not make an applied rename look new again.

Cost. summarisesLoops is off by default, so on a default install nothing was reading transcripts at all; ungated, this would have added a 512KB tail read per loop every fifteen seconds to every install there is, to re-read a name that changes when a human types six characters. Gated on TranscriptFreshness — its own instance, not shared, because hasChanged is consuming and the two readers would otherwise take turns to work — a quiet loop costs one stat.

Deliberately out of scope

  • Remote loops. Reading one costs an ssh probe per node per poll. Worth it for a rail that moves every few seconds, not for a name that changes once in a loop's life. Reported as "nothing said" rather than bought at that price.
  • The other three backends. None of Copilot CLI, Codex, or OpenCode has a rename that leaves a record behind, so they return nil rather than a guess.
  • The reverse direction. An app-side rename is not pushed into the session.

Regression surface

Risk Guard
Clobbering a human's title every 15s sessionTitle change-detection — aRenameFromTheSidebarSurvivesTheNextPoll
Renaming loops from agent-name Record type matched exactly — anAgentNameIsNotARename
The poll starting to write graph.json every tick Persist only when a rename actually landed; the existing thePollMustNotPersistTheGraph still passes
Starving the summary rail's freshness gate Separate TranscriptFreshness instance; SummaryRailTests unchanged and green
Old graphs failing to decode decodeIfPresent, matching every other field on LoopNode

Verification

Check Result
xcodebuild test ✅ 1534 tests / 159 suites, up 11
graphcode / graphcoded / graphcode-cli builds ✅ all three succeed
swiftlint lint ✅ 0 errors
swift format lint --strict ✅ clean

🤖 Generated with Claude Code

https://claude.ai/code/session_011hEyiK8W9XW4Tq6je98kTX

`/rename` inside a Claude Code session changed the session's name and left
the loop's card showing the title it was created with. The title is the one
field a loop is expected to outgrow — written before the work exists — so
the two disagreed exactly when the human had just said what the loop is.

Claude Code writes the answer down: a standalone `custom-title` record,
re-emitted at every checkpoint, in the transcript graphcode already knows how
to find. Read on the presence poll and applied through the same `renameNode`
a human's rename goes through.

Three things this is careful about:

- **`custom-title` only.** Every session also carries `agent-name`, holding a
  name the CLI assigned itself (`angleReuse2`), present in sessions nobody
  renamed. Reading that would retitle most of a graph to strings no human
  typed.
- **Once per change, not once per sight.** `LoopNode.sessionTitle` records the
  last observed session name, so a card renamed from the sidebar afterwards
  keeps its name instead of being overwritten fifteen seconds later. It is
  persisted, so a daemon restart cannot make an applied rename look new.
- **Cost.** The summary rail is off by default, so nothing read transcripts on
  a default install; ungated this would have added a 512KB tail read per loop
  per poll to every install. Gated on `TranscriptFreshness` — its own instance,
  since `hasChanged` is consuming — a quiet loop costs one stat.

Remote loops are left out deliberately: reading one costs an ssh probe per
node per poll, which is worth it for a rail that moves every few seconds and
not for a name that changes when somebody types six characters.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011hEyiK8W9XW4Tq6je98kTX
@scgopi

scgopi commented Sep 3, 2026

Copy link
Copy Markdown
Owner Author

Don't merge the PR yet, need to see if it is low risk

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.

A session renamed with /rename should rename its loop

1 participant