Skip to content

Commit 3847958

Browse files
thymikeeclaude
andauthored
fix: serve artifact downloads at /upload/{id} to fix proxy 404 (#205)
The artifact download endpoint was at GET /artifacts/{id}, but proxies in the sandbox → proxy → daemon architecture only forward known paths (/rpc, /health, /upload). Move downloads to GET /upload/{id} so they share the already-forwarded /upload path (POST for uploads, GET for downloads). Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2522984 commit 3847958

4 files changed

Lines changed: 8 additions & 8 deletions

File tree

src/daemon-client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -915,7 +915,7 @@ function buildDaemonHttpUrl(baseUrl: string, route: 'health' | 'rpc'): string {
915915

916916
function buildDaemonArtifactUrl(baseUrl: string, artifactId: string): string {
917917
const normalizedBase = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
918-
return new URL(`artifacts/${encodeURIComponent(artifactId)}`, normalizedBase).toString();
918+
return new URL(`upload/${encodeURIComponent(artifactId)}`, normalizedBase).toString();
919919
}
920920

921921
async function materializeRemoteArtifacts(

src/daemon/__tests__/http-server.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ test('HTTP artifact download streams registered files', async (t) => {
193193
fs.rmSync(root, { recursive: true, force: true });
194194
});
195195

196-
const response = await callGet(port, `/artifacts/${artifactId}`, {
196+
const response = await callGet(port, `/upload/${artifactId}`, {
197197
authorization: 'Bearer test-token',
198198
});
199199
assert.equal(response.statusCode, 200);
@@ -232,7 +232,7 @@ test('HTTP artifact download rejects requests without the daemon token', async (
232232
fs.rmSync(root, { recursive: true, force: true });
233233
});
234234

235-
const response = await callGet(port, `/artifacts/${artifactId}`);
235+
const response = await callGet(port, `/upload/${artifactId}`);
236236
assert.equal(response.statusCode, 401);
237237
assert.match(response.body, /Invalid token/);
238238
});

src/daemon/http-server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ export async function createDaemonHttpServer(options: {
260260
return;
261261
}
262262

263-
if (req.method === 'GET' && req.url?.startsWith('/artifacts/')) {
263+
if (req.method === 'GET' && req.url?.startsWith('/upload/')) {
264264
void handleArtifactDownload(req, res, authHook, token);
265265
return;
266266
}
@@ -442,7 +442,7 @@ async function handleArtifactDownload(
442442
authHook: HttpAuthHook | null,
443443
expectedToken?: string,
444444
): Promise<void> {
445-
const artifactId = req.url?.slice('/artifacts/'.length) ?? '';
445+
const artifactId = req.url?.slice('/upload/'.length) ?? '';
446446
if (!artifactId) {
447447
res.statusCode = 400;
448448
res.end('Missing artifact id');

src/utils/__tests__/daemon-client.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ test('sendToDaemon downloads remote artifacts and rewrites local paths', async (
487487
callback?.();
488488
return;
489489
}
490-
if (this.options.method === 'GET' && String(this.options.path).includes('/artifacts/')) {
490+
if (this.options.method === 'GET' && String(this.options.path).includes('/upload/')) {
491491
res.end(Buffer.from('png-binary'));
492492
callback?.();
493493
return;
@@ -544,7 +544,7 @@ test('sendToDaemon downloads remote artifacts and rewrites local paths', async (
544544
});
545545

546546
assert.equal(response.ok, true);
547-
assert.deepEqual(seenPaths, ['/agent-device/health', '/agent-device/rpc', '/agent-device/artifacts/artifact-123']);
547+
assert.deepEqual(seenPaths, ['/agent-device/health', '/agent-device/rpc', '/agent-device/upload/artifact-123']);
548548
assert.match(String((rpcRequest as any)?.params?.positionals?.[0]), /^\/tmp\/agent-device-screenshot-/);
549549
assert.equal(
550550
(rpcRequest as any)?.params?.meta?.clientArtifactPaths?.path,
@@ -573,7 +573,7 @@ test('downloadRemoteArtifact times out stalled artifact responses and removes pa
573573
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-remote-artifact-timeout-'));
574574
const destinationPath = path.join(tempRoot, 'artifacts', 'screen.png');
575575
const server = http.createServer((req, _res) => {
576-
if (req.url?.includes('/artifacts/')) {
576+
if (req.url?.includes('/upload/')) {
577577
return;
578578
}
579579
});

0 commit comments

Comments
 (0)