Repository navigation
Fix coding-agent session naming across panes and tabs - #879
Merged
Merged
Conversation
Investigation, load-bearing validation (2 rounds) and task plan for pane/tab names failing to set and reverting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One definition of "this text is a default or derived label, not a real name", in TypeScript (shared/name-evidence.ts) and Rust (freshell_sessions::name_evidence), pinned by one shared fixture table (test/fixtures/name-evidence/cases.json) that both test suites consume. A name is default/derived when, after trimming, it is empty, "Tab N", the basename of a known cwd, a provider/mode label (optionally "Recovered " / " agent"), Recovered/Untitled/Tab, a provider placeholder (OpenCode "New session - <ISO>" etc., scoped to the context provider), or text opening with a known tool wrapper tag. Only the leading-tag check of is_system_context is used (extracted as text::starts_with_context_tag), so names like "$ ls -la" stay real. The OpenCode directory index and the client tab-title "Tab N" check now go through the shared functions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The TypeScript copy of SYSTEM_CONTEXT_LEAD_TAGS (shared/name-evidence.ts) is kept in step with crates/freshell-sessions/src/text.rs only by the shared fixture, which had rows for 2 of the 9 tags. Add one default row per remaining tag, an uppercase and a mixed-case tag row (pins the ASCII lowercasing), a space- and an ideographic-space-terminated row, and a BOM-terminated row that must stay a real name (pins the Rust-matching whitespace set rather than JS \s). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Once the one-time name consolidation has completed, a browser import
(what an old client left open keeps sending: its live tab titles such as
"Tab 23") is acknowledged in full and writes nothing: no record is
created or changed, no candidate id is stored, no backup file is written
and the document generation does not move. A store whose view already
shows the receipt answers without the document lock; a stale view goes
through the transaction, whose decision applies the same rule. One INFO
session_names.import_ignored_after_completion line per import id.
An import-owned record whose name is a recognizable default label
("Tab 4", "OpenCode agent") now loses a bind collision to the live
pending record; other imported names keep the rank rule. Equal-rank,
equal-scope candidates before completion keep the incumbent: the
evidence key (window and device ids) no longer decides a name.
The shared test trace collector also captures this INFO event and
rebuilds callsite interest on every call, closing a tracing-core
registration race that could drop a WARN in the extensions test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ire spells them Task 3 review follow-ups: - Pin both safety conditions of import_owned_label_loses_bind: a protected import-owned "Tab 4" (legacy_protected, manual, freshell_ai) keeps the rank rule, and so does a "Tab 4" that moved past its installed revision. - bind_collision_import_label_lost now logs loser_source/winner_source in their serde wire spelling (provider_ai, directory), not Debug form. - Reword the candidate-order comments: a window or device id never displaces an installed name, but within one boot the candidate-id tie break picks which of several exact equals installs first. - Plan e2e matrix: RC1 row lists name-import-after-completion-rust.spec.ts and the rewritten cross-mode case, so the Task 13 roll-up runs them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and let junk names self-heal
Native titles. OpenCode's temporary "New session - <ISO>" title (and every
other default label) is never saved and never reaches the store from the
live lanes. Each record keeps the last real native title it saw: an echo of
it is provenance only (it can no longer undo a rename, nor re-raise rank),
the first observation of an existing record only records that baseline, and
a provider title that actually changed replaces an older provider-level
name. A baseline that changes nothing else is staged in memory and
persisted by the sweep's maintenance batch, never its own commit.
Observations never stall an event relay: each is built once (location
stamped at observation time), makes one attempt, and a busy store hands the
same observation to a background retry (200 ms, then 1 s) that applies only
while it is still the newest one for its target. The CLI opencode lane's
observer moves out of main.rs into freshell-ws so it reserves that order
synchronously and is tested against the real lane.
Automation renames. An automatic rename now applies at equal rank and
reports `outcome {applied, reason}` on every rename response; it is refused
over a protected name (`protected_name`) or for a default label for the
target, classified with the target's best-known folder (`default_label`),
so a rename to the session's own folder name is refused instead of applied
and later healed away. The CLI prints "name not applied: <reason>" and the
MCP rename actions add a warning.
Self-heal. An unprotected record whose saved name is recognizable junk
("Tab 23", "OpenCode agent", a provider placeholder, the session's own
folder) gives way to the next real automatic name — first message, native
title or the index hydration ladder — whatever its rank, while manual,
migration-protected and Freshell AI names are never touched. The sweep's
pre-checks and the decisions share one plan (the activity's folder
included; the planned fallback is applied as planned, not re-offered
through the strict raise), and heals ride ONE maintenance transaction per
pass of at most 25 records so a post-deploy burst never starves renames.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sidebar and the history page render a session row's directory title whenever no saved name is known yet. For an OpenCode session that title was opencode's temporary "New session - <ISO>" placeholder, so a new session showed the placeholder until its name record arrived — the e2e "shows the provider real title, never the placeholder" caught it on the CLI opencode lane in every attempt. A placeholder (or blank) opencode title is now never the row's title: the row presents the title derived from its first user message — the convention Claude and Codex rows already follow when their transcript has no provider title — else no title. The derived title is not a provider name (`title_source` stays unset), so the naming ladder still treats the session as unnamed. One predicate, `is_opencode_session_name`, now answers "opencode named this session" for both the listing's first-message lookup and the row's provenance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s it The automation-outcome e2e read the new pane's naming handle from the browser's own state and ran `freshell rename-tab` at once. The CLI acts through the server's layout mirror, which adopts a UI-created agent pane only when the browser's layout sync lands; before that the tab route saw a plain tab and renamed its layout label (the documented capture-less behavior), so the saved name stayed the folder fallback and the case failed 3 of 3 attempts on cloud. The spec now waits until the server's layout snapshot carries the pane's naming identity before each CLI call, and asserts the applied rename's response reached the naming record (`data.sessionName.outcome.applied`). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…relay behind a retry Task 4 review follow-ups. A staged native baseline now records the persisted baseline it was staged over. Once another process persists a different baseline, the staged one is dropped: decisions no longer read it and the sweep maintenance batch no longer writes it back over the newer value (which made the provider's next re-report look changed and undo an automation rename). An inline native observation no longer waits for its target's turn. When another submission of the same target is still inside the store, the new observation goes straight to the background retrier, takes the turn as soon as it is free and keeps the latest-only ordering, so a relay's worst case stays one store attempt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ip tool wrapper text A terminal-mode Claude, Codex or OpenCode session now gets its first-message name as soon as its first real message is seen, whether or not AI naming works, and tool wrapper text is never taken as the first message (D4, RC12). - The first-message fallback is offered for every activity that carries a first user message, including the sweep's Opened/Resumed edges for a session open in a live terminal; text the shared classifier recognizes as a default or derived label is never a fallback, nor a hydration rung. - The shared system-context tag lists (and the classifier's TypeScript copy, kept in step by the shared fixture) know Codex's model-context fragments and Claude Code's slash-command and reminder blocks, so every consumer of extract_user_authored_text (titles, search, the AI prompt, first messages, Claude's user-message count) skips them. - The session index re-derives a cached Claude/Codex row whose first message starts with such a tag once, by a per-entry rules stamp (no schema bump; an older binary ignores the field). - A re-derived first message fed by the index to an Idle, never-attempted series rests (re-absorb at rest), so re-deriving history starts no background AI naming; wrapper or derived text is never AI-naming input (activity, bind hook and claim guards). - The sweep's targeted OpenCode first-message lookup also serves a record below first_message or stuck on a junk label under its folder, and remembers each answer across passes per (session, row activity). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sed input
Task 5 review follow-ups:
- The sweep's cross-pass memo of targeted OpenCode first-message reads no
longer remembers a failed read as "no user message". The read is now
Result<Option<String>, _> end to end (a failed prepare/query is an error,
not None); a failure logs WARN session_names.first_message_lookup_failed
and is retried after 60 s, or at once when the row's activity moves.
- load_cache_file logs INFO session_index.first_message_rules_reparse
{dropped, rules_version} when it drops stale tag-led entries.
- session_names.generation_input_refused carries site=activity|bind|claim.
- hydrate_indexed's doc states the re-absorb-at-rest rule.
- The linked-folder first-message e2e case runs for codex and opencode too
(each fails when the open-session fallback is removed).
- The sweep tests move to a child module to keep the test file under 1000
lines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…al or quota failures without consuming attempts AI naming now routes by credential on every attempt: a configured Gemini key (env, else Settings) is always sent, directly past a OneCLI-marked ambient proxy and through any other proxy; only with no key does naming borrow the inherited OneCLI identity (one boot WARN). The OneCLI-then-key retry is gone; one request per attempt. Failures are classified (gemini_failure.rs): 429 is Throttled; OneCLI bodies, 401/403/407 and API_KEY_INVALID are Capability; an untrusted certificate is found by downcasting the rustls error; unreachable proxies and hosts are Capability; timeouts and 5xx stay Transient. Capability and Throttled failures refund the claimed start (attempt id, start time and input kept), and an in-memory pause stops claims until a re-probe window (15 min; quota 60 s doubling to 15 min) or a key change. Exhaustion moved from the claim to the fold, so a refunded third start keeps its series. A key saved in Settings clears the pause and wakes the worker. The JSONL writer redacts the configured key value (and later saved keys) verbatim and recognizes the AQ. key form. The Gemini key input in Settings gets an accessible name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd settle starts re-armed in flight Task 6 review fixes. - The naming pause gets an epoch that every key change bumps. The generator reads it before a request is built; a refusal, quota answer, timeout or answer from an older epoch no longer pauses, re-arms or restores naming (the refusal is still refunded), so a key saved while a request was in flight is tried at the next poll instead of up to 15 minutes later. Logged once at INFO as session_names.generation_outcome_superseded. - A newer first message that re-arms a series while its request is in flight now leaves the series in flight with the new input, and the fold of the old request settles the charged start even though its input is stale: a refusal is refunded by attempt id, a counted outcome exhausts the series at the cap (dropping its input) and otherwise leaves the newer input due at once. Boot recovery still finds such a start, so a crash at the cap still ends exhausted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Name reads (`get`, the read route, every projection) now answer from the store's adopted view instead of taking the cross-process document lock and re-parsing the whole 4.5 MB document. The view remembers the stamp of the file it was adopted from (Unix: device + inode + length + nanosecond mtime; Windows: length + mtime), so a read only stats the document; a changed stamp, or an equal one older than the 30 s full-check window, runs the existing strict locked adoption. A read never waits for the lock: when another participant is mid-commit it answers from the current view. Fences (unreconciled, vanished or rewritten documents) still fail reads as before. The read route no longer runs refresh_current, and the 2 s tick uses the same cheap peek (adopt_external_if_changed). refresh_current stays for the publisher's lag recovery. Nothing in the document is pruned. Adds a live-size (>= 4.5 MB) name document to the e2e fixture writer and a smoke spec where three pages reload during a rename burst. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…erited lock holds Review fixes for the lock-free name reads (name-reliability Task 11): - The full read runs the stamp peek again once it holds the lock, so a read whose unlocked stat caught a commit halfway no longer re-parses the whole document just to confirm the same generation. - The INFO `session_names.stamp_missed_change` fires only when the adopted file's stamp equals the previously adopted one; an ordinary commit that lands between the peek and the lock is logged as the DEBUG `adopted_external`. - The 2 s naming tick moved from main.rs into session_name_tick.rs, with a test that it takes no store transaction while the document is unchanged and that it adopts another process's commit. - Reads now try a busy lock for up to 10 ms instead of once. A child process forked while a transaction held the lock keeps an inherited copy of the lock until it execs, which made cross-process reads (and fence reads) answer from the stale view. - Test support: the scoped trace capture keeps a second tracing dispatcher alive, so call sites first reached on other threads are not cached as uninteresting; reads that must see another writer's change are repeated only when a test tally proves the lock was busy. - The module doc no longer claims that every commit changes the stamp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every terminal row the server creates or adopts now binds its saved name through one shared helper (freshell-freshagent terminal_name_binding) that both doors call: the WebSocket create, the REST create, resume, the auto-resume respawn, the managed register, reconcile and compatibility adoptions. The helper writes the identity table and the registry row together, reads the record into the row's cache and title (the saved name, else the mode label, never "Shell"), retries a transient store error (50 ms, 250 ms, 1 s) and then defers to the tick with the binding kept: a failed read never falls into the fresh pending path, and a failed ensure_pending still leaves the row bound to its pending handle. A conversation switch (Claude /clear, an OpenCode switch, a new Codex thread) moves the row's registry binding with the identity ref and the row adopts the new session's record or its mode label; the client releases the superseded conversation's pending naming handle on an authorized rebind so the pane shows the new session's name. A 2 s reconcile tick backstops unreported moves, binds scoped rows that have no binding after a create grace period, and fills bound rows with an empty name cache. The hosted fresh-agent proxy logs a boot WARN that its naming stays incomplete. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A naming read that a newer move superseded is now dropped instead of applied: the create, adoption, conversation-switch and tick paths write a read's answer only while the row is still bound to the ref that was read (NamingBindOutcome::Superseded otherwise), the pending fallback never re-binds a row a switch moved meanwhile (both binding homes are compared), and a switch onto a session with no record keeps a record the naming publisher wrote in between. A fresh managed Claude create's own preallocated --session-id, which the supervisor echoes in its descriptor, is no longer treated as an established durable session (WebSocket and REST admissions share one rule); the tick likewise binds a record-less door-known session through a pending handle. The tick skips exited rows and backs off rows bound to a durable session with no record yet (doubling, capped near 64 s). The auto-resume respawn inherits the crashed row's still-pending naming handle, so a name given before the first message survives the crash. The client keeps a pane's pending handle across a conversation switch until the server has spent it (its redirect is cached), matching the server row in a zero-turn switch. Also: the REST naming tests move to terminal_tabs_naming_tests.rs, the terminal_naming_tests declaration no longer splits another module's doc comment, and the claude names spec imports the shared types from the right path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ut saves The browser kept treating its own live layout as "legacy" name evidence: every reload, new window, sibling-window layout write and machine-workspace rebuild copied the current per-window layout into a migration envelope and POSTed it to /api/session-names/import, where a session-owned tab's stored title (for example "Tab 6") replaced the session's saved name (RC1). The copies were never deleted, and once one could not be written the layout save itself was deferred, so renames were lost on reload (RC10). - New src/lib/legacy-name-capture-policy.ts owns when evidence is captured: at most once per device (durable marker freshell.session-names.capture-done.v1), only from the genuine pre-migration keys (bare freshell.layout.v3 family, freshell.tabs.v2, freshell.panes.v2) whose bytes carry no unified-names fields; never a per-window key, its channels or its pre-migration sidecar. Acknowledged and non-genuine envelopes are deleted; a device that captured before never captures again. Write failures log legacy_name_capture_failed and never throw. - Candidate extraction skips default/derived labels (shared classifier, with the pane's folder and provider) and a session-owned tab's title. - Removed the crossTabSync and machine-workspace capture calls and the persistence flush gate; submission runs only until the device is done. - Tests: new policy unit suite, rewritten migration/flush/crossTabSync/ machine-workspace cases, and two cloud e2e specs (name-import-shutdown-rust: reload, second window, stale per-window titles; name-storage-quota-rust: near-full storage with stale envelopes). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ery import POST Task 2 review follow-ups: - The boot-time legacy-name capture runs before main.tsx installs client log forwarding, so its legacy_name_capture_failed errors reached only the browser console. The policy module now keeps this page load's failures, and main.tsx replays them once (replayed: true) right after forwarding is installed. - The capture policy said a failed capture is retried on the next boot. In the real flow the next connection-ready submit finds nothing pending and marks the device done, so the evidence is dropped (the accepted RC10 tradeoff; the server acknowledges post-completion imports without writing). The module comment and the two unit cases now say so, and a separate case pins the one real retry: a page load that never reached a ready connection. - The import-shutdown e2e attached its import counters after the second and stale windows had already booted. One context-wide counter, attached before the first navigation, now counts every page's POSTs per page. - New e2e: a boot capture that fails on quota lands in the server's JSONL log (rust-server.jsonl) as a forwarded client_log entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Claude/Codex/OpenCode tab's stored title now follows the session's saved name. A new middleware (savedNameTabTitleMirror) writes the cached saved record's name into a session-owned tab's title, without bumping updatedAt and without the user-rename flag, so the persisted layout, the server layout mirror (GET /api/tabs) and the tabs registry carry the saved name, and a reload shows it before names load. It reads records only (selectSessionTabSavedNames): with no cached record the tab keeps its last-known title and never stores a provider label or folder name. One gate, canWriteLegacyTabTitle, keeps every legacy tab-title writer (terminal OSC and registry titles, the shell exit suffix, openSessionTab reopen retitles, the kilroy first-message name) off session-owned tabs. A tab.create ui.command no longer freezes the title of a scoped agent tab. The registry overlay uses the records-only selector. updateTab accepts touchUpdatedAt:false for display-only refreshes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ning legacy tab-title writers
The saved-name tab title mirror now remembers which naming ref each
mirrored title came from. When a session-owned tab's naming ref changes
to one with no cached saved name (a conversation switch such as /clear, a
new conversation in the pane, or the naming source moving to another
pane), the title returns to the unnamed placeholder ("Tab N"), so the tab
bar, the registry, the persisted layout and the server layout mirror show
the new session's fallback instead of the old session's name. A tab's
first naming identity never resets its create-time title. An empty cached
name no longer triggers a no-op mirror write.
The sidebar click on an unscoped session row, and the unscoped terminal
rename from the overview and its context menu, now ask
canWriteLegacyTabTitle, so a session-owned tab's stored title is written
only by the mirror. The registry overlay comment no longer overstates
what the built title can be.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every WebSocket ready and runtime broadcast ran the managed-runtime merge,
which wrote the supervisor view's title ("Shell agent", "OpenCode agent")
over the matched tab's stored title, bumped the tab's updatedAt, and took
tab-level fields from whichever pane matched.
- The merge never writes a title. Only the tab's naming pane (its session
source pane, else its first leaf) drives tab-level fields, and those
runtime facts are applied without touching updatedAt.
- A reconstructed scoped agent tab is session-owned from the start
(nameSource) and stores no title, so the saved-name mirror fills it once
the record is known; other reconstructed tabs keep only a real view
title, never a default or derived label.
- The managed terminal inventory's tab status refresh (every ready) no
longer changes recency either.
- The server requests managed views without a title (one shared builder
for the terminal and fresh-agent launches), and the supervisor no longer
invents a "Recovered <X> agent" fallback; the now-dead provider
parameters are removed.
- Tests: merge unit cases, the inventory recency case, a supervisor
registry case, a server launch case against a synthetic supervisor, the
cloud spec managed-runtime-title-merge and the local-only Docker spec
managed-runtime-names-local-rust (registered in LOCAL_ONLY_SPECS). The
tab rename helper tolerates the editor closing mid-resubmit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The "unrenamed shell tab never shows Shell agent" case recorded the stored tab (title and updatedAt) as soon as the pane had a terminal id. A shell whose first prompt sets an OSC window title makes a legitimate legacy write after that point, so the final "refresh changed nothing" comparison could fail without a product bug. Wait for the prompt and for two stored-tab reads a second apart to agree before recording the baseline and the label. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Task 8 stops the managed-runtime merge from writing "Shell agent" and similar labels, but titles an earlier merge already stored on unrenamed shell tabs are not rewritten, and getTabDisplayTitle still shows them. Record that gap, how it heals (the shell's next window title), and the possible client-only follow-up, so the coordinator can decide. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opening Freshell in a new window (or any recovery-inventory rebuild) now brings back shell, browser and editor tab and pane names together with their "user renamed" protection, so the shell's next window title can no longer replace them. - Registry pane snapshots carry the pane's titleSetByUser (schema, open and closed records, tabs.sync push, live local records). - The recovery inventory forwards each pane's title and user flag and the tab's user flag (new recovery_inventory_titles module); none of them is part of the dismissal digest. - buildRecoveryPlan restores pane titles and flags remapped through the pane-id map, drops scoped agent panes' titles (stripScopedPaneTitleMetadata) and restores the tab flag only on tabs that are not session-owned. - One shared restore step (applyRecoveryTabPlan) for the machine-workspace rebuild and the recovery offer. - local-first-reload e2e: mango's pane title is automatic so the cross-window step still exercises an automatic title now that rebuilds keep user flags; scenario 2 asserts the restored flags. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A registry pane carrying titleSetByUser without a recorded title would come back frozen at its derived label, so later automatic titles (OSC, browser page title) never applied. The rebuild now drops such a flag and logs the count at debug level. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's local turn (FIFO) serializes a store's own transactions before the cross-process lock, and its three local-serialization tests used `get` as the queued same-process caller. On this branch a name read never queues: it answers from the adopted view (name-reliability Task 11), so those tests failed after the merge (the read returned the pre-commit name at once and no slow-turn warning was logged). The tests now queue a pure-read transaction (`refresh_current`) through a `read_through_transaction` helper, so they still prove what main pinned: same-process transactions wait their turn in arrival order and a stalled turn is reported while the caller is still queued. New test a_read_never_queues_behind_this_stores_own_transaction pins the integrated read rule: while this store's own transaction holds the turn, a read whose peek saw a changed document answers from the view at once (class local_turn_busy) and the next read after the commit answers its result. A mutation that makes reads await the turn fails it (the read returns the slow commit's name after 1.5 s); a mutation that skips the turn and tries only the document lock fails it too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oops The live log's repair-trigger flood was one reconnect loop per codex pane: the TUI polls plugin/list, whose ~17.8 MB response exceeded the proxy's old 16 MiB transport cap, so every connection died and the TUI reconnected. #850 raised the caps and #851 classified and rate-limited over-cap errors; the live log has shown no loop since that build. This test drives the incident shape (a TUI that reconnects after every close, polling an upstream that answers with the 17.8 MB frame) on production defaults and requires one connection and no repair trigger. With the old 16 MiB cap it fails with 127 reconnects in 5 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every accepted saved-name change now logs one INFO session_names.name_changed after the durable commit: the record key, the old and new name, source and revision, the cause (import, native_observation, first_message_fallback, generation, manual_rename, automation_rename, bind, bind_collision, ensure_pending, hydrate, adopted_external), a self_heal flag, and from_ref / import_id / evidence_key / series when they apply. Each store decision attaches the change to its publish entry; the transaction runner logs it only after the replace succeeded, and logs another process's changes once when this store adopts them (never the document loaded at open). A conversation switch, a refused or unchanged offer, a post-completion import and a failed commit log nothing. The ad-hoc self_heal_applied and bind_collision_import_label_lost lines are superseded and removed (the latter was logged before the commit). E2e specs assert the events for the first-message name, the OpenCode placeholder and the self-heal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…it-time adoption Task 12 review fixes: - log_name_changes merged several changes of one record by taking the old name from the first change but judging self_heal against the last change's prior. It now merges with NameChangeEvent::after (the sweep batch's rule), and emit reads the old fields and the flag from the event's single prior, so they can no longer disagree. - Tests: a commit that lands over another process's newer document logs that change once as adopted_external beside its own; an import with two winning candidates for one session logs one change from the record before the import; a sweep item that heals by index then by message logs one self-heal from the junk name; the merge rule itself; and the self_heal flag is the same for a folder-name to first-message raise whether the session index or the first-message fallback made it. - Docs: logging.rs and session_name_events state that name_changed logs saved names (at most 200 characters; a first-message name is the first words of the first user message, cut to 50) and give the exact self_heal definition. Co-Authored-By: Claude Opus 5.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.
Saved names could diverge between a pane and its tab, follow the wrong conversation after a switch or restore, or be replaced by wrapper text. This change gives each Claude, Codex, and OpenCode session one durable saved name across panes, tabs, restored layouts, continuation, registry reconciliation, and managed-runtime views.
Automatic titles now filter tool and provider wrapper text, preserve the original first user message for later naming, and repair recognizable generated junk. Generation uses the Gemini key configured in Settings; credential and quota failures pause without consuming retry attempts. Pane and tab rename requests use the current session binding and retain the existing ownership safeguards.
The branch is rebased onto main's FreshClaude native UUID and lifecycle behavior. Regression coverage includes browser and protocol journeys, provider naming paths, and restored legacy rename flows.
Validation:
pnpm run checkpassed onbe363af5: cloud Vitest 9,725 passed across four shards; local companion 19 passed and one existing opt-in test skipped; source-runtime 10 passed; Rust workspace and Electron 395 passed.be363af5: Rust formatting, Clippy, workspace tests (excluding the Tauri crate, per the hook), and TypeScript typecheck.28c5d151. The only later change is the CLI MCP trace regression test and its synchronization fix, covered by the full check above.