Skip to content

Commit 75e2b3b

Browse files
Merge pull request #1290 from claude-code-best/fixture/worktree
fix: 修复 ExecuteTool 委托 null result 导致的 worktree UI 崩溃及 debug 日志 ENOENT
2 parents 7680c29 + f9e77eb commit 75e2b3b

4 files changed

Lines changed: 35 additions & 6 deletions

File tree

packages/builtin-tools/src/tools/EnterWorktreeTool/UI.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,17 @@ export function renderToolResultMessage(
1414
_progressMessagesForMessage: ProgressMessage<ToolProgressData>[],
1515
_options: { theme: ThemeName },
1616
): React.ReactNode {
17+
if (!output) return null;
1718
return (
1819
<Box flexDirection="column">
1920
<Text>
20-
Switched to worktree on branch <Text bold>{output.worktreeBranch}</Text>
21+
Switched to worktree
22+
{output.worktreeBranch ? (
23+
<>
24+
{' '}
25+
on branch <Text bold>{output.worktreeBranch}</Text>
26+
</>
27+
) : null}
2128
</Text>
2229
<Text dimColor>{output.worktreePath}</Text>
2330
</Box>

packages/builtin-tools/src/tools/ExecuteTool/ExecuteTool.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,12 @@ export const ExecuteTool = buildTool({
250250
renderToolResultMessage(content, progressMessages, options) {
251251
const innerTool = options.tools.find(t => t.name === content.tool_name)
252252
if (!innerTool?.renderToolResultMessage) return null
253+
// Guard against null/undefined result — several error branches in this
254+
// tool (tool-not-found, validation-failed, permission-denied, etc.) set
255+
// result: null, and delegating null to the inner tool's UI would crash
256+
// on any property access (e.g. output.worktreeBranch).
257+
if (content.result == null || typeof content.result !== 'object')
258+
return null
253259
const innerInput = (options.input as { params?: unknown } | undefined)
254260
?.params
255261
return innerTool.renderToolResultMessage(

packages/builtin-tools/src/tools/ExitWorktreeTool/UI.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export function renderToolResultMessage(
1414
_progressMessagesForMessage: ProgressMessage<ToolProgressData>[],
1515
_options: { theme: ThemeName },
1616
): React.ReactNode {
17+
if (!output) return null;
1718
const actionLabel = output.action === 'keep' ? 'Kept worktree' : 'Removed worktree';
1819
return (
1920
<Box flexDirection="column">

src/utils/debug.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { appendFile, mkdir, symlink, unlink } from 'fs/promises'
22
import memoize from 'lodash-es/memoize.js'
3-
import { dirname, join } from 'path'
3+
import { dirname, join, resolve } from 'path'
44
import { getSessionId } from 'src/bootstrap/state.js'
55

66
import { type BufferedWriter, createBufferedWriter } from './bufferedWriter.js'
@@ -90,10 +90,10 @@ export const getDebugFilePath = memoize((): string | null => {
9090
for (let i = 0; i < process.argv.length; i++) {
9191
const arg = process.argv[i]!
9292
if (arg.startsWith('--debug-file=')) {
93-
return arg.substring('--debug-file='.length)
93+
return resolve(arg.substring('--debug-file='.length))
9494
}
9595
if (arg === '--debug-file' && i + 1 < process.argv.length) {
96-
return process.argv[i + 1]!
96+
return resolve(process.argv[i + 1]!)
9797
}
9898
}
9999
return null
@@ -170,7 +170,20 @@ function getDebugWriter(): BufferedWriter {
170170
// Directory already exists
171171
}
172172
}
173-
getFsImplementation().appendFileSync(path, content)
173+
try {
174+
getFsImplementation().appendFileSync(path, content)
175+
} catch (e) {
176+
// If the directory was removed between mkdirSync and
177+
// appendFileSync (or the path was relative and CWD changed),
178+
// retry once with a fresh mkdirSync. Swallow on second failure
179+
// to avoid crashing the React render tree via the error boundary.
180+
try {
181+
getFsImplementation().mkdirSync(dir)
182+
getFsImplementation().appendFileSync(path, content)
183+
} catch {
184+
// Best-effort: debug log write failed, don't crash the UI
185+
}
186+
}
174187
void updateLatestDebugLogSymlink()
175188
return
176189
}
@@ -228,7 +241,9 @@ export function logForDebugging(
228241
export function getDebugLogPath(): string {
229242
return (
230243
getDebugFilePath() ??
231-
process.env.CLAUDE_CODE_DEBUG_LOGS_DIR ??
244+
(process.env.CLAUDE_CODE_DEBUG_LOGS_DIR
245+
? resolve(process.env.CLAUDE_CODE_DEBUG_LOGS_DIR)
246+
: null) ??
232247
join(getClaudeConfigHomeDir(), 'debug', `${getSessionId()}.txt`)
233248
)
234249
}

0 commit comments

Comments
 (0)