Skip to content

feat: resume/fork sessions by UUID or thread name#22

Merged
aannoo merged 12 commits intoaannoo:mainfrom
bloodcarter:feat/resume-by-session-and-thread-name
Apr 18, 2026
Merged

feat: resume/fork sessions by UUID or thread name#22
aannoo merged 12 commits intoaannoo:mainfrom
bloodcarter:feat/resume-by-session-and-thread-name

Conversation

@bloodcarter
Copy link
Copy Markdown
Contributor

Summary

Extends hcom r <name> and hcom f <name> to accept three new forms in addition to hcom instance names:

  1. Session UUIDhcom r 521cfc2b-be38-403a-b32e-4a49c9551b27 — searches Claude, Codex, and Gemini transcript dirs on disk, extracts the last working directory from the transcript, and relaunches with full hcom wrapping (PTY, hooks, message delivery).

  2. Claude Code thread namehcom r skills-work — scans ~/.claude/projects/*/*.jsonl for {\"type\":\"custom-title\",\"customTitle\":\"...\"} entries (created by Claude Code's /rename command). Picks the most recently modified match.

  3. Codex thread namehcom r stabilization-review — looks up ~/.codex/session_index.jsonl for a matching thread_name. Picks the most recently updated match.

Resolution order: hcom instance → UUID → Claude thread → Codex thread. hcom f (fork) gets the same resolution for free.

Why

Useful when a session was started outside of hcom (or /rename'd inside Claude Code) and you want to bring it under hcom management without remembering the UUID. Example:

# In Claude Code, user runs /rename and titles the session \"skills-work\"
# Later, from any terminal:
hcom r skills-work
# → Resolves to the session UUID, relaunches in the original directory under hcom

Implementation notes

  • Active-instance guard prevents resuming a session that is already running under another hcom instance.
  • claude_config_dir and detect_agent_type promoted to pub(crate) in transcript.rs so resume.rs can reuse them (instead of duplicating).
  • Session-ID resume bypasses the preview/RPC remote-dispatch flow — it's a local-only operation (the remote side doesn't have the transcript).
  • build_resume_args is shared with the normal resume path, so argv construction (--resume, -c experimental_resume, etc.) stays consistent.
  • Gemini transcripts don't store CWD; the script warns and falls back to the current directory (or --dir override).

Tests

5 new unit tests:

  • test_is_session_id_valid — accepts UUIDs
  • test_is_session_id_rejects_names — rejects 4-letter hcom names, dashed names, empty strings
  • test_extract_last_cwd_claude — picks the latest cwd field across JSONL lines
  • test_extract_last_cwd_codex — picks the latest payload.cwd
  • test_extract_last_cwd_gemini — returns None (no CWD in Gemini transcripts)

Codex/Claude thread-name resolution tests are inline in the original feature branch but were not included here because they can't override dirs::home_dir() / claude_config_dir() cleanly. The parsing logic is covered by the integration path.

All 33 existing + new commands::resume::tests pass (cargo test --release commands::resume).

Test plan

  • cargo build --release succeeds
  • cargo test --release commands::resume — 33 passed
  • hcom r <real-claude-uuid> relaunches in original directory
  • hcom r <renamed-claude-title> resolves via custom-title scan
  • hcom r <codex-thread-name> resolves via session_index.jsonl
  • hcom f <session-id> forks into current directory (not original)
  • hcom r <active-session-id> errors out with a clear message

🤖 Generated with Claude Code

bloodcarter and others added 12 commits April 17, 2026 09:35
Extends `hcom r <name>` and `hcom f <name>` to accept:

  1. A session UUID (e.g. `hcom r 521cfc2b-be38-403a-b32e-4a49c9551b27`)
     — searches Claude, Codex, and Gemini transcript directories on
     disk, extracts the last working directory from the transcript,
     and relaunches with hcom wrapping.

  2. A Claude Code thread name (e.g. `hcom r skills-work`) — scans
     ~/.claude/projects/*/*.jsonl for `{"type":"custom-title"}` entries
     (created by Claude Code's /rename command), picks the most recent
     match by file mtime.

  3. A Codex thread name (e.g. `hcom r stabilization-review`) — looks
     up ~/.codex/session_index.jsonl for a matching `thread_name`, picks
     the most recent match by `updated_at`.

Resolution order: hcom instance → UUID → Claude thread → Codex thread.

Fork (`hcom f`) gets the same resolution for free. An active-instance
guard prevents resuming a session that is already running under another
hcom instance.

Makes `claude_config_dir` and `detect_agent_type` `pub(crate)` so they
can be reused from resume.rs (was the old pattern — duplication avoided).

5 new tests covering UUID detection and CWD extraction from Claude and
Codex transcripts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the parallel do_resume_by_session_id path with a single
ResumeSource enum fed into prepare_resume_plan_from_source, so
fork/resume/identity logic stays in one place.

- Removed thread-name resolvers (Claude and Codex resolve titles
  natively via their own --resume flags)
- Added opencode adoption: queries SQLite DB for session directory
- CWD recovery reads first transcript line only (O(1) vs full walk)
- Added Gemini CWD recovery via sha256(cwd) reverse lookup in
  projects.json (was incorrectly assumed impossible)
- Adoption uses system_prompt: None — SessionStart hook handles
  bootstrap (no prior hcom identity in transcript to override)
- Fixed exit code propagation in adoption path
- Fixed append_reply_handoff for adoption-fork-codex
- Fixed preview text: adoption says "new hcom identity" not "same"
Re-adds Claude custom-title and Codex thread-name resolution to
`hcom r <name>`. Now errors when both tools match the same name
instead of silently picking Claude. Routes resolved UUIDs through
the shared adoption path (collision guard, CWD recovery, etc.).
Real Claude transcripts start with `{"type":"permission-mode",...}` which
carries no `cwd` — it first appears on line 2+. Reading only line 1 caused
adoption to silently fall back to $PWD. Scan up to 20 lines until a
non-empty `cwd` is found; leave codex's line-1 path unchanged.
Opencode uses the xdg-basedir npm package, which follows XDG on every
platform — so sessions live at \`~/.local/share/opencode\` on macOS, not
\`~/Library/Application Support/opencode\`. \`dirs::data_dir\` returned the
latter, making adopt-by-UUID miss real sessions. Check
\$XDG_DATA_HOME, then \`~/.local/share/opencode\`, then the platform
default, returning the first that exists.
Adopting by UUID used to allocate a fresh 4-letter name whenever the
prior binding had cascaded away — i.e. after every stop/kill. Fall back
to scanning life.stopped events for a snapshot with the same session_id
and delegate to the name-based resume path when found, so \`hcom r <uuid>\`
resumes under the original identity.

Also switch the collision-guard error messages from 'run hcom stop' to
'run hcom kill' to match project convention, and reword the surrounding
comment to describe the lookaside vs. source-of-truth split honestly.
find_session_on_disk short-circuits for \`ses_\`-prefixed opencode IDs —
only opencode is actually searched. Printing all four tool paths on miss
was misleading. Scope the "Searched:" list to match the actual probe.
handle_remote_resume used to call run_local_resume_result which went
straight to prepare_resume_plan with an Instance source — skipping the
UUID/binding/events-fallback/thread-name/adoption chain that do_resume
applies on the initiating device. hcom r <uuid>:DEVICE and
hcom r <thread-name>:DEVICE bailed with "No stopped snapshot found"
even when the session existed on the target.

Rewrite run_local_resume_result to mirror do_resume's resolution order
and return a LaunchResult via new sibling adopt_session_result. Extract
the shared plan-building into build_adopt_plan so interactive and RPC
adoption stay in lockstep.
- Claude: scan all custom-title entries per transcript and only match when
  the LAST one equals the requested name. Renaming a session A → B → C
  used to leave \`hcom r A\` resolving to it; now only \`hcom r C\` does.
- Claude + Codex: when multiple distinct sessions currently share a
  thread name, bail with an ambiguity error listing the candidate session
  IDs and their last-touched timestamps. Previously the mtime/updated_at
  tiebreaker silently picked one — easy to accidentally resume the wrong
  session after duplicate renames.
- F1: last \`hcom stop\` straggler in prepare_resume_plan_from_source's
  active-instance bail is now \`hcom kill\`.
- F2: stale session_bindings row no longer shortcuts to a name-based
  resume. Bail only on the active case; otherwise fall through to the
  life.stopped events lookup — events are the source of truth.
- F3: extract resolve_name_to_plan. do_resume and run_local_resume_result
  each called resolve_display_name_or_stopped / is_session_id / binding
  check / events lookup / adoption separately; a bug fix (F1's kill
  wording, F2's stale-binding hazard) had to be threaded through both.
  Now one helper walks the chain as a bounded loop and returns
  (resolved_name, PreparedResume) for both paths. The old
  do_adopt_session / adopt_session_result wrappers go away.
- F4: load_stopped_snapshot now filters action='stopped' in SQL and
  ORDER BY id DESC LIMIT 1. Old version fetched the last 10 life events
  and scanned in Rust — the stopped snapshot could fall off the window
  after many relaunches. Mirrors find_stopped_instance_by_session_id.
- F5: resolve_thread_name no longer prints "Resolved Claude/Codex
  thread" to stderr on every successful lookup. Routed to log_info.
- F6: clarify in a comment that the fork cursor-anchoring block
  intentionally skips adoption-fork — there's no pre-reg row to
  protect; launcher::launch already sets HCOM_LAUNCH_EVENT_ID so the
  SessionStart hook creates the fresh row with the correct cursor.
Both help blocks used to say only "Resume a stopped agent by name" /
"Fork an agent session" with no mention of UUID adoption, Claude
/rename titles, Codex thread names, the :<device> suffix, or any of
the --dir / --headless / --run-here / --hcom-prompt / --hcom-system-prompt
flags that the resume parser accepts. A new user running \`hcom r --help\`
had no way to discover those forms.

Also factor SHARED_LAUNCH_FLAGS out so \`hcom <tool> --help\` and
\`hcom r/f --help\` don't drift apart — the tool help now also lists
--run-here, and both pick up flag additions in one place.
@aannoo aannoo merged commit a2c5a1d into aannoo:main Apr 18, 2026
10 checks passed
@aannoo
Copy link
Copy Markdown
Owner

aannoo commented Apr 18, 2026

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.

2 participants