Skip to content

Commit 07f124a

Browse files
authored
fix: workspace creation failing with misleading error (#136)
1 parent 2232ea1 commit 07f124a

File tree

3 files changed

+98
-1
lines changed

3 files changed

+98
-1
lines changed

src/agent/router.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ export interface RouterContext {
217217

218218
function mapErrorToORPC(err: unknown, defaultMessage: string): never {
219219
const message = err instanceof Error ? err.message : defaultMessage;
220-
if (message.includes('not found')) {
220+
if (message.match(/Workspace '.*' not found/)) {
221221
throw new ORPCError('NOT_FOUND', { message: 'Workspace not found' });
222222
}
223223
if (message.includes('already exists')) {

src/workspace/manager.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ async function ensureWorkspaceImage(): Promise<string> {
4545
return WORKSPACE_IMAGE_LOCAL;
4646
}
4747

48+
const registryExists = await docker.imageExists(registryImage);
49+
if (registryExists) {
50+
return registryImage;
51+
}
52+
4853
console.log(`Pulling workspace image ${registryImage}...`);
4954
const pulled = await docker.tryPullImage(registryImage);
5055
if (pulled) {

test/unit/error-mapping.test.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { ORPCError } from '@orpc/server';
3+
4+
function mapErrorToORPC(err: unknown, defaultMessage: string): never {
5+
const message = err instanceof Error ? err.message : defaultMessage;
6+
if (message.match(/Workspace '.*' not found/)) {
7+
throw new ORPCError('NOT_FOUND', { message: 'Workspace not found' });
8+
}
9+
if (message.includes('already exists')) {
10+
throw new ORPCError('CONFLICT', { message });
11+
}
12+
throw new ORPCError('INTERNAL_SERVER_ERROR', { message });
13+
}
14+
15+
describe('mapErrorToORPC', () => {
16+
it('maps workspace not found errors to NOT_FOUND', () => {
17+
const error = new Error("Workspace 'my-workspace' not found");
18+
expect(() => mapErrorToORPC(error, 'Failed')).toThrow(ORPCError);
19+
20+
try {
21+
mapErrorToORPC(error, 'Failed');
22+
} catch (e) {
23+
expect(e).toBeInstanceOf(ORPCError);
24+
expect((e as ORPCError).code).toBe('NOT_FOUND');
25+
expect((e as ORPCError).message).toBe('Workspace not found');
26+
}
27+
});
28+
29+
it('does not map workspace image not found to NOT_FOUND', () => {
30+
const error = new Error('Workspace image not found. Either:\n 1. Run perry build locally');
31+
expect(() => mapErrorToORPC(error, 'Failed')).toThrow(ORPCError);
32+
33+
try {
34+
mapErrorToORPC(error, 'Failed');
35+
} catch (e) {
36+
expect(e).toBeInstanceOf(ORPCError);
37+
expect((e as ORPCError).code).toBe('INTERNAL_SERVER_ERROR');
38+
expect((e as ORPCError).message).toContain('Workspace image not found');
39+
}
40+
});
41+
42+
it('does not map generic not found errors to NOT_FOUND', () => {
43+
const error = new Error('File not found');
44+
expect(() => mapErrorToORPC(error, 'Failed')).toThrow(ORPCError);
45+
46+
try {
47+
mapErrorToORPC(error, 'Failed');
48+
} catch (e) {
49+
expect(e).toBeInstanceOf(ORPCError);
50+
expect((e as ORPCError).code).toBe('INTERNAL_SERVER_ERROR');
51+
expect((e as ORPCError).message).toBe('File not found');
52+
}
53+
});
54+
55+
it('maps already exists errors to CONFLICT', () => {
56+
const error = new Error("Workspace 'test' already exists");
57+
expect(() => mapErrorToORPC(error, 'Failed')).toThrow(ORPCError);
58+
59+
try {
60+
mapErrorToORPC(error, 'Failed');
61+
} catch (e) {
62+
expect(e).toBeInstanceOf(ORPCError);
63+
expect((e as ORPCError).code).toBe('CONFLICT');
64+
expect((e as ORPCError).message).toContain('already exists');
65+
}
66+
});
67+
68+
it('maps unknown errors to INTERNAL_SERVER_ERROR', () => {
69+
const error = new Error('Something went wrong');
70+
expect(() => mapErrorToORPC(error, 'Failed')).toThrow(ORPCError);
71+
72+
try {
73+
mapErrorToORPC(error, 'Failed');
74+
} catch (e) {
75+
expect(e).toBeInstanceOf(ORPCError);
76+
expect((e as ORPCError).code).toBe('INTERNAL_SERVER_ERROR');
77+
expect((e as ORPCError).message).toBe('Something went wrong');
78+
}
79+
});
80+
81+
it('uses default message for non-Error objects', () => {
82+
expect(() => mapErrorToORPC('string error', 'Default error message')).toThrow(ORPCError);
83+
84+
try {
85+
mapErrorToORPC('string error', 'Default error message');
86+
} catch (e) {
87+
expect(e).toBeInstanceOf(ORPCError);
88+
expect((e as ORPCError).code).toBe('INTERNAL_SERVER_ERROR');
89+
expect((e as ORPCError).message).toBe('Default error message');
90+
}
91+
});
92+
});

0 commit comments

Comments
 (0)