1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//! Hook-correlation helpers that write Claude session ids to managed records.
//!
//! Why: `manager.rs` was at 500 SLOC (the production cap) after the
//! `claude_session_id` field was added; adding `set_claude_session_id` there
//! would breach the cap. Following the same pattern as `adopt.rs` (a sibling
//! `impl SessionManager` block), this file isolates the one write-path needed
//! by the hook relay — keeping all other files under their SLOC budgets.
//! What: two inherent methods — `SessionManager::set_claude_session_id` looks
//! up the record, writes `claude_session_id`, and persists atomically;
//! `SessionManager::clear_claude_session_id_if` (#4337) clears it again, but
//! only when the stored value still matches an expected id, so a
//! `SessionEnd` can safely un-correlate its OWN session without racing a
//! concurrent, fresher correlation.
//! Test: `claude_session_id_persists_on_session`,
//! `clear_claude_session_id_if_clears_on_exact_match`,
//! `clear_claude_session_id_if_leaves_a_different_id_untouched` in
//! `super::tests`.
use super::manager::{ManagedError, SessionManager};
use super::record::ManagedSessionId;
impl SessionManager {
/// Store the Claude Code internal session UUID on a managed session record.
///
/// Why (#1744): the `SessionStart` hook delivers Claude Code's own session
/// UUID via `CLAUDE_SESSION_ID`. Persisting it lets `resume` pass
/// `--resume <id>` to the new `claude` process, restoring the prior
/// conversation even after an ungraceful exit (terminal kill, tmux pane
/// closed without `/quit`). Without this id, resume launches FRESH — #6765
/// removed the `--continue` fallback.
/// What: looks up the record, sets `claude_session_id`, and persists.
/// No tmux I/O — the hook handler calls this after correlating the
/// `SessionStart` event to the right managed session.
/// Test: `claude_session_id_persists_on_session` in `super::tests`.
pub async fn set_claude_session_id(
&self,
id: &ManagedSessionId,
claude_session_id: &str,
) -> Result<(), ManagedError> {
let mut r = self.get(id).await?;
r.claude_session_id = Some(claude_session_id.to_owned());
self.store.write().await.upsert(r).await.map_err(Into::into)
}
/// Clear a managed record's `claude_session_id`, but ONLY if it still
/// equals `expected` (#4337).
///
/// Why: `SessionEnd` is Claude Code's own signal that `expected`'s
/// conversation has ended, so the id is guaranteed stale from that point
/// on — leaving it in place would let a later `SessionStart` from an
/// unrelated process (or a stale resume) be silently compared against a
/// dead id forever, permanently blocking re-correlation. Clearing it here
/// lets the NEXT `SessionStart` for this record's cwd freely take the
/// "no existing id" branch in `daemon::api::session_start_correlation`.
/// The exact-match guard prevents clobbering a FRESHER id that a
/// concurrent, later `SessionStart` may have already written between the
/// caller's own match check and this call.
/// What: no-op (`Ok(())`) when the record is missing, already `None`, or
/// holds a DIFFERENT id than `expected`. Test:
/// `clear_claude_session_id_if_clears_on_exact_match`,
/// `clear_claude_session_id_if_leaves_a_different_id_untouched` in
/// `super::tests`.
pub async fn clear_claude_session_id_if(
&self,
id: &ManagedSessionId,
expected: &str,
) -> Result<(), ManagedError> {
let mut r = self.get(id).await?;
if r.claude_session_id.as_deref() != Some(expected) {
return Ok(());
}
r.claude_session_id = None;
self.store.write().await.upsert(r).await.map_err(Into::into)
}
}