Skip to content

Commit e8ed674

Browse files
fix(proxy): convert 404 NAMESPACE_NOT_FOUND to 200 empty results (DAK-6950) (#227)
Playground sandbox proxy now intercepts 404 responses from the engine that contain "Namespace not found" or "NAMESPACE_NOT_FOUND" and converts them to 200 with {"memories":[],"note":"namespace_initializing"}. This makes all playground modes resilient to the seed→recall race condition without requiring engine changes. Non-namespace 404s are passed through unchanged. Tests: 60/60 pass (2 new: conversion + passthrough). 🤖 [Agent: CTO] Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3ebab85 commit e8ed674

2 files changed

Lines changed: 61 additions & 6 deletions

File tree

docker/playground/proxy/proxy.test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,3 +1088,42 @@ test('response agent_id is restored to the client original per scenario (DAK-692
10881088

10891089
await p.close();
10901090
});
1091+
1092+
// ---------------------------------------------------------------------------
1093+
// DAK-6950: proxy converts 404 NAMESPACE_NOT_FOUND to 200 empty results
1094+
// ---------------------------------------------------------------------------
1095+
1096+
test('404 NAMESPACE_NOT_FOUND from engine is converted to 200 with empty memories (DAK-6950)', async () => {
1097+
const p = await startProxy({}, (req, res) => {
1098+
res.writeHead(404, { 'Content-Type': 'application/json' });
1099+
res.end(JSON.stringify({ error: 'Namespace not found', message: 'Namespace not found: sandbox_abc_cmp' }));
1100+
});
1101+
const session = 'pg_ns404test';
1102+
const res = await request(p.port, {
1103+
method: 'POST',
1104+
path: '/v1/memory/recall',
1105+
headers: { 'content-type': 'application/json', 'x-playground-session': session },
1106+
body: JSON.stringify({ agent_id: 'test_cmp', query: 'test', top_k: 5 }),
1107+
});
1108+
assert.equal(res.status, 200, 'proxy should convert 404 namespace-not-found to 200');
1109+
const json = JSON.parse(res.body);
1110+
assert.deepStrictEqual(json.memories, [], 'memories should be empty array');
1111+
assert.equal(json.note, 'namespace_initializing', 'note should indicate namespace is initializing');
1112+
await p.close();
1113+
});
1114+
1115+
test('404 for non-namespace errors is passed through unchanged (DAK-6950)', async () => {
1116+
const p = await startProxy({}, (req, res) => {
1117+
res.writeHead(404, { 'Content-Type': 'application/json' });
1118+
res.end(JSON.stringify({ error: 'not_found', message: 'Route not found' }));
1119+
});
1120+
const session = 'pg_404passtest';
1121+
const res = await request(p.port, {
1122+
method: 'POST',
1123+
path: '/v1/memory/recall',
1124+
headers: { 'content-type': 'application/json', 'x-playground-session': session },
1125+
body: JSON.stringify({ agent_id: 'test_cmp', query: 'test', top_k: 5 }),
1126+
});
1127+
assert.equal(res.status, 404, 'non-namespace 404 should pass through');
1128+
await p.close();
1129+
});

docker/playground/proxy/server.js

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,20 +157,36 @@ function forward(config, req, res, path, bodyBuf, baseHeaders, rewrite) {
157157
outHeaders[k] = v;
158158
}
159159

160-
// Fast path: no agent_id was injected — stream straight through.
161-
if (!rewrite) {
160+
// DAK-6950: intercept 404 NAMESPACE_NOT_FOUND from the engine and convert
161+
// to 200 with empty results. Avoids exposing engine-internal namespace errors
162+
// to playground users whose seed data may not have landed yet.
163+
const isNs404 = upRes.statusCode === 404;
164+
165+
// Fast path: no agent_id was injected — stream straight through (unless 404).
166+
if (!rewrite && !isNs404) {
162167
res.writeHead(upRes.statusCode || 502, outHeaders);
163168
upRes.pipe(res);
164169
return;
165170
}
166171

167-
// Namespaced request: buffer the response so we can restore the client's
168-
// original agent_id before returning it (DAK-6757). Sandbox payloads are
169-
// small, so buffering here is cheap.
172+
// Namespaced request (or potential 404): buffer the response so we can
173+
// restore agent_id and/or intercept namespace-not-found (DAK-6757, DAK-6950).
170174
const chunks = [];
171175
upRes.on('data', (c) => chunks.push(c));
172176
upRes.on('end', () => {
173-
const buf = restoreResponseAgentId(Buffer.concat(chunks), rewrite.namespace, rewrite.restoreTo);
177+
let buf = Buffer.concat(chunks);
178+
if (isNs404) {
179+
const bodyStr = buf.toString('utf8');
180+
if (bodyStr.indexOf('amespace not found') >= 0 || bodyStr.indexOf('NAMESPACE_NOT_FOUND') >= 0) {
181+
const empty = JSON.stringify({ memories: [], note: 'namespace_initializing' });
182+
outHeaders['content-length'] = String(Buffer.byteLength(empty));
183+
delete outHeaders['transfer-encoding'];
184+
res.writeHead(200, outHeaders);
185+
res.end(empty);
186+
return;
187+
}
188+
}
189+
if (rewrite) buf = restoreResponseAgentId(buf, rewrite.namespace, rewrite.restoreTo);
174190
outHeaders['content-length'] = String(buf.length);
175191
delete outHeaders['transfer-encoding'];
176192
res.writeHead(upRes.statusCode || 502, outHeaders);

0 commit comments

Comments
 (0)