Skip to content

Commit fc21c87

Browse files
committed
fix: preserve wire error codes verbatim (addresses codex review)
The initial weak-types pass validated wire error codes against a closed union, silently downgrading any unknown code to COMMAND_FAILED. This dropped signals like AMBIGUOUS_MATCH that handlers emit and clients are documented to handle (skills/agent-device/references/exploration.md). - Widen AppErrorCode to 'KnownAppErrorCode | (string & {})' so autocomplete of known codes is preserved while any wire code flows through - toAppErrorCode now preserves any non-empty code; fallback only when undefined or empty - Add AMBIGUOUS_MATCH to KnownAppErrorCode (documented public code) - Add test coverage for preservation and fallback behavior
1 parent 1344c00 commit fc21c87

2 files changed

Lines changed: 20 additions & 25 deletions

File tree

src/utils/__tests__/errors.test.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { test } from 'vitest';
22
import assert from 'node:assert/strict';
3-
import { AppError, asAppError, normalizeError } from '../errors.ts';
3+
import { AppError, asAppError, normalizeError, toAppErrorCode } from '../errors.ts';
44

55
test('normalizeError adds default hint and strips diagnostic metadata from details', () => {
66
const err = new AppError('COMMAND_FAILED', 'runner failed', {
@@ -91,3 +91,15 @@ test('normalizeError provides app discovery guidance for app-not-installed error
9191
/Run apps to discover the exact installed package or bundle id/i,
9292
);
9393
});
94+
95+
test('toAppErrorCode preserves handler-emitted codes verbatim (including AMBIGUOUS_MATCH)', () => {
96+
assert.equal(toAppErrorCode('AMBIGUOUS_MATCH'), 'AMBIGUOUS_MATCH');
97+
assert.equal(toAppErrorCode('SOME_FUTURE_CODE'), 'SOME_FUTURE_CODE');
98+
assert.equal(toAppErrorCode('DEVICE_IN_USE'), 'DEVICE_IN_USE');
99+
});
100+
101+
test('toAppErrorCode falls back when code is missing or empty', () => {
102+
assert.equal(toAppErrorCode(undefined), 'COMMAND_FAILED');
103+
assert.equal(toAppErrorCode(''), 'COMMAND_FAILED');
104+
assert.equal(toAppErrorCode(undefined, 'UNAUTHORIZED'), 'UNAUTHORIZED');
105+
});

src/utils/errors.ts

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { redactDiagnosticData } from './redaction.ts';
22

3-
export type AppErrorCode =
3+
export type KnownAppErrorCode =
44
| 'INVALID_ARGS'
55
| 'DEVICE_NOT_FOUND'
66
| 'DEVICE_IN_USE'
@@ -12,35 +12,18 @@ export type AppErrorCode =
1212
| 'COMMAND_FAILED'
1313
| 'SESSION_NOT_FOUND'
1414
| 'UNAUTHORIZED'
15+
| 'AMBIGUOUS_MATCH'
1516
| 'UNKNOWN';
1617

17-
const APP_ERROR_CODES: readonly AppErrorCode[] = [
18-
'INVALID_ARGS',
19-
'DEVICE_NOT_FOUND',
20-
'DEVICE_IN_USE',
21-
'TOOL_MISSING',
22-
'APP_NOT_INSTALLED',
23-
'UNSUPPORTED_PLATFORM',
24-
'UNSUPPORTED_OPERATION',
25-
'NOT_IMPLEMENTED',
26-
'COMMAND_FAILED',
27-
'SESSION_NOT_FOUND',
28-
'UNAUTHORIZED',
29-
'UNKNOWN',
30-
];
31-
32-
/**
33-
* Narrow a wire-level error code string into a known AppErrorCode.
34-
* Unrecognised codes are surfaced verbatim by preserving them on details,
35-
* while the typed channel falls back to the provided default.
36-
*/
18+
// Accepts any string so wire codes from the daemon are preserved verbatim; the
19+
// `(string & {})` form keeps IDE autocomplete of the known codes.
20+
export type AppErrorCode = KnownAppErrorCode | (string & {});
21+
3722
export function toAppErrorCode(
3823
code: string | undefined,
3924
fallback: AppErrorCode = 'COMMAND_FAILED',
4025
): AppErrorCode {
41-
if (typeof code === 'string' && (APP_ERROR_CODES as readonly string[]).includes(code)) {
42-
return code as AppErrorCode;
43-
}
26+
if (typeof code === 'string' && code.length > 0) return code;
4427
return fallback;
4528
}
4629

0 commit comments

Comments
 (0)