Skip to content
This repository was archived by the owner on Feb 14, 2026. It is now read-only.

Commit 6e7942a

Browse files
committed
fix(cli): add session path tracking to prevent crashes during parallel tasks
When Task subagents create sessions in different directories (e.g., worktrees), the session validation now uses the path where the session was created rather than the current working directory. This fixes "Process exited unexpectedly" errors when mobile users send messages during parallel prompt execution with worktrees. Changes: - Add SessionInfo interface with id and path fields - Update Session class to track session creation path - Modify claudeCheckSession to accept optional sessionPath - Extract cwd from SDKSystemMessage for session tracking
1 parent f9cb121 commit 6e7942a

File tree

4 files changed

+36
-17
lines changed

4 files changed

+36
-17
lines changed

src/claude/claudeRemote.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export async function claudeRemote(opts: {
1616
// Fixed parameters
1717
sessionId: string | null,
1818
path: string,
19+
sessionPath?: string,
1920
mcpServers?: Record<string, any>,
2021
claudeEnvVars?: Record<string, string>,
2122
claudeArgs?: string[],
@@ -31,7 +32,7 @@ export async function claudeRemote(opts: {
3132
isAborted: (toolCallId: string) => boolean,
3233

3334
// Callbacks
34-
onSessionFound: (id: string) => void,
35+
onSessionFound: (id: string, sessionPath?: string) => void,
3536
onThinkingChange?: (thinking: boolean) => void,
3637
onMessage: (message: SDKMessage) => void,
3738
onCompletionEvent?: (message: string) => void,
@@ -40,7 +41,7 @@ export async function claudeRemote(opts: {
4041

4142
// Check if session is valid
4243
let startFrom = opts.sessionId;
43-
if (opts.sessionId && !claudeCheckSession(opts.sessionId, opts.path)) {
44+
if (opts.sessionId && !claudeCheckSession(opts.sessionId, opts.path, opts.sessionPath)) {
4445
startFrom = null;
4546
}
4647

@@ -178,10 +179,11 @@ export async function claudeRemote(opts: {
178179
// Start a watcher for to detect the session id
179180
if (systemInit.session_id) {
180181
logger.debug(`[claudeRemote] Waiting for session file to be written to disk: ${systemInit.session_id}`);
181-
const projectDir = getProjectPath(opts.path);
182+
const sessionCwd = systemInit.cwd ?? opts.path;
183+
const projectDir = getProjectPath(sessionCwd);
182184
const found = await awaitFileExist(join(projectDir, `${systemInit.session_id}.jsonl`));
183185
logger.debug(`[claudeRemote] Session file found: ${systemInit.session_id} ${found}`);
184-
opts.onSessionFound(systemInit.session_id);
186+
opts.onSessionFound(systemInit.session_id, sessionCwd);
185187
}
186188
}
187189

@@ -235,4 +237,4 @@ export async function claudeRemote(opts: {
235237
} finally {
236238
updateThinking(false);
237239
}
238-
}
240+
}

src/claude/claudeRemoteLauncher.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ export async function claudeRemoteLauncher(session: Session): Promise<'switch' |
327327
const remoteResult = await claudeRemote({
328328
sessionId: session.sessionId,
329329
path: session.path,
330+
sessionPath: session.sessionInfo?.path,
330331
allowedTools: session.allowedTools ?? [],
331332
mcpServers: session.mcpServers,
332333
hookSettingsPath: session.hookSettingsPath,
@@ -363,10 +364,10 @@ export async function claudeRemoteLauncher(session: Session): Promise<'switch' |
363364
// Exit
364365
return null;
365366
},
366-
onSessionFound: (sessionId) => {
367+
onSessionFound: (sessionId, sessionPath) => {
367368
// Update converter's session ID when new session is found
368369
sdkToLogConverter.updateSessionId(sessionId);
369-
session.onSessionFound(sessionId);
370+
session.onSessionFound(sessionId, sessionPath);
370371
},
371372
onThinkingChange: session.onThinkingChange,
372373
claudeEnvVars: session.claudeEnvVars,
@@ -457,4 +458,4 @@ export async function claudeRemoteLauncher(session: Session): Promise<'switch' |
457458
}
458459

459460
return exitReason || 'exit';
460-
}
461+
}

src/claude/session.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import { MessageQueue2 } from "@/utils/MessageQueue2";
33
import { EnhancedMode } from "./loop";
44
import { logger } from "@/ui/logger";
55

6+
interface SessionInfo {
7+
id: string;
8+
path: string;
9+
}
10+
611
export class Session {
712
readonly path: string;
813
readonly logPath: string;
@@ -17,7 +22,7 @@ export class Session {
1722
/** Path to temporary settings file with SessionStart hook (required for session tracking) */
1823
readonly hookSettingsPath: string;
1924

20-
sessionId: string | null;
25+
sessionInfo: SessionInfo | null;
2126
mode: 'local' | 'remote' = 'local';
2227
thinking: boolean = false;
2328

@@ -33,6 +38,7 @@ export class Session {
3338
path: string,
3439
logPath: string,
3540
sessionId: string | null,
41+
sessionPath?: string,
3642
claudeEnvVars?: Record<string, string>,
3743
claudeArgs?: string[],
3844
mcpServers: Record<string, any>,
@@ -46,7 +52,10 @@ export class Session {
4652
this.api = opts.api;
4753
this.client = opts.client;
4854
this.logPath = opts.logPath;
49-
this.sessionId = opts.sessionId;
55+
this.sessionInfo = opts.sessionId ? {
56+
id: opts.sessionId,
57+
path: opts.sessionPath ?? opts.path
58+
} : null;
5059
this.queue = opts.messageQueue;
5160
this.claudeEnvVars = opts.claudeEnvVars;
5261
this.claudeArgs = opts.claudeArgs;
@@ -61,6 +70,10 @@ export class Session {
6170
this.client.keepAlive(this.thinking, this.mode);
6271
}, 2000);
6372
}
73+
74+
get sessionId(): string | null {
75+
return this.sessionInfo?.id ?? null;
76+
}
6477

6578
/**
6679
* Cleanup resources (call when session is no longer needed)
@@ -93,8 +106,11 @@ export class Session {
93106
* Updates internal state, syncs to API metadata, and notifies
94107
* all registered callbacks (e.g., SessionScanner) about the change.
95108
*/
96-
onSessionFound = (sessionId: string) => {
97-
this.sessionId = sessionId;
109+
onSessionFound = (sessionId: string, sessionPath?: string) => {
110+
this.sessionInfo = {
111+
id: sessionId,
112+
path: sessionPath ?? this.path
113+
};
98114

99115
// Update metadata with Claude Code session ID
100116
this.client.updateMetadata((metadata) => ({
@@ -130,7 +146,7 @@ export class Session {
130146
* Clear the current session ID (used by /clear command)
131147
*/
132148
clearSessionId = (): void => {
133-
this.sessionId = null;
149+
this.sessionInfo = null;
134150
logger.debug('[Session] Session ID cleared');
135151
}
136152

@@ -176,4 +192,4 @@ export class Session {
176192
this.claudeArgs = filteredArgs.length > 0 ? filteredArgs : undefined;
177193
logger.debug(`[Session] Consumed one-time flags, remaining args:`, this.claudeArgs);
178194
}
179-
}
195+
}

src/claude/utils/claudeCheckSession.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { existsSync, readFileSync } from "node:fs";
33
import { join } from "node:path";
44
import { getProjectPath } from "./path";
55

6-
export function claudeCheckSession(sessionId: string, path: string) {
7-
const projectDir = getProjectPath(path);
6+
export function claudeCheckSession(sessionId: string, path: string, sessionPath?: string) {
7+
const projectDir = getProjectPath(sessionPath ?? path);
88

99
// Check if session id is in the project dir
1010
const sessionFile = join(projectDir, `${sessionId}.jsonl`);
@@ -25,4 +25,4 @@ export function claudeCheckSession(sessionId: string, path: string) {
2525
});
2626

2727
return hasGoodMessage;
28-
}
28+
}

0 commit comments

Comments
 (0)