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
2 changes: 1 addition & 1 deletion src/cli/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function git(args: string[], cwd: string = process.cwd()): string {
return execGitNonInteractive(args, { cwd });
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Git command failed: git ${args.join(' ')}\n${message}`);
throw new Error(`Git command failed: git ${args.join(' ')}\n${message}`, { cause: error });
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/evals/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export function loadEvalFile(filePath: string): EvalFile {
try {
content = readFileSync(filePath, 'utf-8');
} catch (error) {
throw new Error(`Failed to read ${filePath}: ${error}`);
throw new Error(`Failed to read ${filePath}: ${error}`, { cause: error });
}

let parsed: unknown;
Expand Down
5 changes: 3 additions & 2 deletions src/sdk/analyze.ts
Original file line number Diff line number Diff line change
Expand Up @@ -524,13 +524,14 @@ async function analyzeHunk(
const errorMessage = error instanceof Error ? error.message : String(error);
throw new WardenAuthenticationError(
`Claude Code subprocess failed (${errorMessage}).\n` +
`This usually means the claude CLI cannot run in this environment.`
`This usually means the claude CLI cannot run in this environment.`,
{ cause: error }
);
}

// Authentication errors should surface immediately with helpful guidance
if (isAuthenticationError(error)) {
throw new WardenAuthenticationError();
throw new WardenAuthenticationError(undefined, { cause: error });
}

// Don't retry if not a retryable error or we've exhausted retries
Expand Down
6 changes: 4 additions & 2 deletions src/sdk/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,16 @@ export function verifyAuth({ apiKey }: { apiKey?: string }): void {
if (isNotFound) {
throw new WardenAuthenticationError(
'Claude Code CLI not found on PATH.\n' +
'Either install Claude Code (https://claude.ai/install.sh) or set an API key.'
'Either install Claude Code (https://claude.ai/install.sh) or set an API key.',
{ cause: error }
);
}
const detail =
error instanceof ExecError ? error.stderr : (error as Error).message;
throw new WardenAuthenticationError(
`Claude Code CLI found but failed to execute: ${detail}\n` +
'Check that the claude binary has correct permissions and can run in this environment.'
'Check that the claude binary has correct permissions and can run in this environment.',
{ cause: error }
);
}
}
4 changes: 2 additions & 2 deletions src/sdk/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ export function isSubprocessError(error: unknown): boolean {
}

export class WardenAuthenticationError extends Error {
constructor(sdkError?: string) {
constructor(sdkError?: string, options?: { cause?: unknown }) {
const message = sdkError
? `Authentication failed: ${sdkError}\n${AUTH_ERROR_GUIDANCE}`
: `Authentication required.${AUTH_ERROR_GUIDANCE}`;
super(message);
super(message, options);
this.name = 'WardenAuthenticationError';
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/skills/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ function execGit(args: string[], options?: { cwd?: string }): string {
return execGitNonInteractive(args, { cwd: options?.cwd });
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new SkillLoaderError(`Git command failed: git ${args.join(' ')}: ${message}`);
throw new SkillLoaderError(`Git command failed: git ${args.join(' ')}: ${message}`, { cause: error });
}
}

Expand Down
10 changes: 6 additions & 4 deletions src/utils/exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ export class ExecError extends Error {
public readonly command: string,
public readonly exitCode: number | null,
public readonly stderr: string,
public readonly signal: string | null
public readonly signal: string | null,
public readonly code?: string,
options?: { cause?: unknown }
) {
const details = stderr || (signal ? `Killed by signal ${signal}` : 'Unknown error');
super(`Command failed: ${command}\n${details}`);
super(`Command failed: ${command}\n${details}`, options);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ExecError.code type too narrow

Low Severity

ExecError adds a code?: string, but Node’s ErrnoException.code can be a string or a numeric errno. Storing it as only string can drop or misrepresent numeric codes, making the new “structured errno access” unreliable in some spawn failure cases.

Fix in Cursor Fix in Web

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

False positive. Node's ErrnoException.code is typed as string | undefined (e.g. "ENOENT", "EACCES"). The numeric value is on the separate errno property, not code. The current typing is correct.

this.name = 'ExecError';
}
}
Expand Down Expand Up @@ -69,7 +71,7 @@ export function execNonInteractive(command: string, options?: ExecOptions): stri
});

if (result.error) {
throw new ExecError(command, null, result.error.message, null);
throw new ExecError(command, null, result.error.message, null, (result.error as NodeJS.ErrnoException).code, { cause: result.error });
}

if (result.status !== 0) {
Expand Down Expand Up @@ -103,7 +105,7 @@ export function execFileNonInteractive(
const result = spawnSync(file, args, spawnOptions);

if (result.error) {
throw new ExecError(command, null, result.error.message, null);
throw new ExecError(command, null, result.error.message, null, (result.error as NodeJS.ErrnoException).code, { cause: result.error });
}

if (result.status !== 0) {
Expand Down