Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion packages/builtin-tools/src/tools/EnterWorktreeTool/UI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@ export function renderToolResultMessage(
_progressMessagesForMessage: ProgressMessage<ToolProgressData>[],
_options: { theme: ThemeName },
): React.ReactNode {
if (!output) return null;
return (
<Box flexDirection="column">
<Text>
Switched to worktree on branch <Text bold>{output.worktreeBranch}</Text>
Switched to worktree
{output.worktreeBranch ? (
<>
{' '}
on branch <Text bold>{output.worktreeBranch}</Text>
</>
) : null}
</Text>
<Text dimColor>{output.worktreePath}</Text>
</Box>
Expand Down
6 changes: 6 additions & 0 deletions packages/builtin-tools/src/tools/ExecuteTool/ExecuteTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,12 @@ export const ExecuteTool = buildTool({
renderToolResultMessage(content, progressMessages, options) {
const innerTool = options.tools.find(t => t.name === content.tool_name)
if (!innerTool?.renderToolResultMessage) return null
// Guard against null/undefined result — several error branches in this
// tool (tool-not-found, validation-failed, permission-denied, etc.) set
// result: null, and delegating null to the inner tool's UI would crash
// on any property access (e.g. output.worktreeBranch).
if (content.result == null || typeof content.result !== 'object')
return null
const innerInput = (options.input as { params?: unknown } | undefined)
?.params
return innerTool.renderToolResultMessage(
Expand Down
1 change: 1 addition & 0 deletions packages/builtin-tools/src/tools/ExitWorktreeTool/UI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export function renderToolResultMessage(
_progressMessagesForMessage: ProgressMessage<ToolProgressData>[],
_options: { theme: ThemeName },
): React.ReactNode {
if (!output) return null;
const actionLabel = output.action === 'keep' ? 'Kept worktree' : 'Removed worktree';
return (
<Box flexDirection="column">
Expand Down
25 changes: 20 additions & 5 deletions src/utils/debug.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { appendFile, mkdir, symlink, unlink } from 'fs/promises'
import memoize from 'lodash-es/memoize.js'
import { dirname, join } from 'path'
import { dirname, join, resolve } from 'path'
import { getSessionId } from 'src/bootstrap/state.js'

import { type BufferedWriter, createBufferedWriter } from './bufferedWriter.js'
Expand Down Expand Up @@ -90,10 +90,10 @@ export const getDebugFilePath = memoize((): string | null => {
for (let i = 0; i < process.argv.length; i++) {
const arg = process.argv[i]!
if (arg.startsWith('--debug-file=')) {
return arg.substring('--debug-file='.length)
return resolve(arg.substring('--debug-file='.length))
}
if (arg === '--debug-file' && i + 1 < process.argv.length) {
return process.argv[i + 1]!
return resolve(process.argv[i + 1]!)
}
}
return null
Expand Down Expand Up @@ -170,7 +170,20 @@ function getDebugWriter(): BufferedWriter {
// Directory already exists
}
}
getFsImplementation().appendFileSync(path, content)
try {
getFsImplementation().appendFileSync(path, content)
} catch (e) {
// If the directory was removed between mkdirSync and
// appendFileSync (or the path was relative and CWD changed),
// retry once with a fresh mkdirSync. Swallow on second failure
// to avoid crashing the React render tree via the error boundary.
try {
getFsImplementation().mkdirSync(dir)
getFsImplementation().appendFileSync(path, content)
} catch {
// Best-effort: debug log write failed, don't crash the UI
}
}
void updateLatestDebugLogSymlink()
return
}
Expand Down Expand Up @@ -228,7 +241,9 @@ export function logForDebugging(
export function getDebugLogPath(): string {
return (
getDebugFilePath() ??
process.env.CLAUDE_CODE_DEBUG_LOGS_DIR ??
(process.env.CLAUDE_CODE_DEBUG_LOGS_DIR
? resolve(process.env.CLAUDE_CODE_DEBUG_LOGS_DIR)
: null) ??
join(getClaudeConfigHomeDir(), 'debug', `${getSessionId()}.txt`)
)
}
Expand Down