mirror of
https://github.com/katanemo/plano.git
synced 2026-06-08 14:55:14 +02:00
fix(claude-cli): use a fresh UUID per spawn for claude --session-id
`--no-session-persistence` only blocks resumability — Claude Code still writes `~/.claude/projects/<workspace>/<id>.jsonl` for every session. Reusing our deterministic brightstaff session id (a v5 UUID hashed from the conversation prefix) caused the CLI to fail every second request for the same conversation with `Error: Session ID ... is already in use`. Generate a per-spawn random v4 UUID inside `ClaudeProcess::spawn` and pass that to `claude --session-id` (and stamp it on every stdin JSONL event so the CLI accepts the turn). Keep the deterministic brightstaff session id as the `SessionManager` map key so retries still hit the hot child.
This commit is contained in:
parent
2aa9981f46
commit
5e689fed51
2 changed files with 42 additions and 11 deletions
|
|
@ -105,7 +105,16 @@ pub struct ClaudeProcess {
|
|||
/// which keeps `SessionManager` callers from holding the session-map lock
|
||||
/// across an async hop.
|
||||
last_used: StdMutex<Instant>,
|
||||
/// Brightstaff-internal identifier — a deterministic UUID v5 derived from
|
||||
/// the conversation prefix (or supplied by the client header). Stable
|
||||
/// across retries so the manager can route follow-up turns to this same
|
||||
/// child. NEVER passed to `claude` itself.
|
||||
pub session_id: String,
|
||||
/// Per-spawn random UUID v4 passed to `claude --session-id`. Always fresh
|
||||
/// so we never collide with on-disk state (`~/.claude/projects/...`)
|
||||
/// from a previous run of the same conversation. Also stamped onto every
|
||||
/// stdin JSONL event so the CLI can verify the turn matches its session.
|
||||
cli_session_id: String,
|
||||
}
|
||||
|
||||
impl ClaudeProcess {
|
||||
|
|
@ -119,6 +128,14 @@ impl ClaudeProcess {
|
|||
cwd: Option<&std::path::Path>,
|
||||
config: ClaudeCliConfig,
|
||||
) -> Result<Arc<Self>, ProcessError> {
|
||||
// Always hand the CLI a brand-new UUID. `--no-session-persistence`
|
||||
// does NOT actually prevent Claude Code from writing
|
||||
// `~/.claude/projects/<workspace>/<id>.jsonl` — it only blocks
|
||||
// resumability — so re-using our deterministic `session_id` would
|
||||
// collide with any prior run of the same conversation and the CLI
|
||||
// would exit with `Session ID ... is already in use`.
|
||||
let cli_session_id = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
let mut cmd = Command::new(&config.binary);
|
||||
cmd.arg("-p")
|
||||
.arg("--output-format")
|
||||
|
|
@ -132,7 +149,7 @@ impl ClaudeProcess {
|
|||
.arg("--model")
|
||||
.arg(normalize_model_arg(model))
|
||||
.arg("--session-id")
|
||||
.arg(&session_id)
|
||||
.arg(&cli_session_id)
|
||||
.arg("--no-session-persistence");
|
||||
|
||||
if let Some(prompt) = system_prompt {
|
||||
|
|
@ -226,6 +243,7 @@ impl ClaudeProcess {
|
|||
|
||||
info!(
|
||||
session = %session_id,
|
||||
cli_session = %cli_session_id,
|
||||
model = %normalize_model_arg(model),
|
||||
"spawned claude-cli"
|
||||
);
|
||||
|
|
@ -237,9 +255,19 @@ impl ClaudeProcess {
|
|||
config,
|
||||
last_used: StdMutex::new(Instant::now()),
|
||||
session_id,
|
||||
cli_session_id,
|
||||
}))
|
||||
}
|
||||
|
||||
/// The UUID that `claude --session-id` was launched with. The bridge has
|
||||
/// to stamp every stdin JSONL event with this id so the CLI accepts the
|
||||
/// turn as belonging to its current session — see
|
||||
/// [`Self::session_id`] for why this is distinct from the brightstaff
|
||||
/// session id.
|
||||
pub fn cli_session_id(&self) -> &str {
|
||||
&self.cli_session_id
|
||||
}
|
||||
|
||||
/// Write the user-turn JSONL events to the child's stdin and return a
|
||||
/// stream that yields parsed CLI events for this turn until the terminal
|
||||
/// `result` event (or watchdog) ends it.
|
||||
|
|
|
|||
|
|
@ -134,16 +134,19 @@ async fn handle(
|
|||
}
|
||||
};
|
||||
|
||||
let stdin_payload = match messages_request_to_stdin_payload(&parsed, Some(&session_id)) {
|
||||
Ok(p) => p,
|
||||
Err(err) => {
|
||||
warn!(error = %err, "failed to build claude-cli stdin payload");
|
||||
return Ok(json_error(
|
||||
StatusCode::BAD_REQUEST,
|
||||
&format!("failed to build claude-cli stdin payload: {err}"),
|
||||
));
|
||||
}
|
||||
};
|
||||
// Stamp stdin events with the CLI's per-spawn UUID, NOT our deterministic
|
||||
// brightstaff session id. The CLI rejects the turn if the two disagree.
|
||||
let stdin_payload =
|
||||
match messages_request_to_stdin_payload(&parsed, Some(process.cli_session_id())) {
|
||||
Ok(p) => p,
|
||||
Err(err) => {
|
||||
warn!(error = %err, "failed to build claude-cli stdin payload");
|
||||
return Ok(json_error(
|
||||
StatusCode::BAD_REQUEST,
|
||||
&format!("failed to build claude-cli stdin payload: {err}"),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let streaming = parsed.stream.unwrap_or(false);
|
||||
let model = parsed.model.clone();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue