duckbrain remember ... --attr='{"liveproof":"rc=254 unchanged"}' fails with Error: --attr must be valid JSON even though the JSON is valid. The JSON was never the problem: the CLI flag parser splits each --flag=value token on every =, discarding everything from the second = onward. The value handed to JSON.parse is a truncated, invalid fragment.
Diagnosed and verified. The repo isn't present in this sandbox, so I reconstructed the parser and proved both the bug and the fix with runnable harnesses. Solution written to ~/duckbrain-attr-equals-fix.md:
--attr value containing = rejected as "must be valid JSON"duckbrain remember ... --attr='{"live_proof":"rc=254 unchanged"}' fails with Error: --attr must be valid JSON even though the JSON is valid. The JSON was never the problem: the CLI flag parser splits each --flag=value token on every =, discarding everything from the second = onward. The value handed to JSON.parse is a truncated, invalid fragment.
The shell strips the single quotes, so the process sees this single argv token:
--attr={"tick":447,"judge":"PARTIAL f3209ad3","live_proof":"rc=254 unchanged"}
The parser does:
const [name, value] = arg.slice(2).split('=');
split('=') returns one element per =. Destructuring into [name, value] keeps only elements 0 and 1, so the value is cut at the first = inside the JSON string:
name = "attr"
value = '{"tick":447,"judge":"PARTIAL f3209ad3","live_proof":"rc'
JSON.parse('{"tick":447,...,"live_proof":"rc') throws, and the generic catch prints --attr must be valid JSON. The message is accurate for the string it received — it just isn't the string the user passed.
This explains the three-way bisection exactly:
| Payload | Contains = in value? |
Result |
|---|---|---|
--attr='{"tick":447}' |
no | works |
--attr='{"a":"has spaces"}' |
no (spaces stay inside one argv token) | works |
--attr='{"a":"rc=254 unchanged"}' |
yes | fails |
Split only on the first =. Replace the naive split('=') in the remember flag parser (bin/duckbrain.js).
Add a helper:
// Split "--name=value" into its name and its (full) value.
// Only the first '=' is a separator; later '=' characters belong to value.
function splitFlagToken(arg) {
const body = arg.slice(2); // drop leading "--"
const eq = body.indexOf('=');
if (eq === -1) return { name: body, value: true };
return { name: body.slice(0, eq), value: body.slice(eq + 1) };
}
Change the parsing loop:
// BEFORE (buggy: truncates at the first '=' inside the value)
for (const arg of argv) {
if (arg.startsWith('--')) {
const [name, value] = arg.slice(2).split('=');
opts[name] = value;
}
}
// AFTER (correct: everything after the first '=' is the value)
for (const arg of argv) {
if (arg.startsWith('--')) {
const { name, value } = splitFlagToken(arg);
opts[name] = value;
}
}
If there is a dedicated --attr branch instead:
// BEFORE
const value = arg.split('=')[1]; // truncates
// AFTER
const value = arg.slice(arg.indexOf('=') + 1); // keeps the whole JSON
Caller-side workaround (already applied): don't put
=inside an--attrvalue (writerc 254/exitcode 254). The parser fix removes the restriction entirely, so attributes may legitimately contain=, URLs, base64,key=valuepairs, etc.
node -e '
const arg = `--attr={"tick":447,"judge":"PARTIAL f3209ad3","live_proof":"rc=254 unchanged"}`;
const [name, value] = arg.slice(2).split("=");
console.log("value handed to JSON.parse:", JSON.stringify(value));
try { JSON.parse(value); console.log("parsed OK"); }
catch { console.log("JSON.parse FAILED -> Error: --attr must be valid JSON"); }
'
Observed:
value handed to JSON.parse: "{\"tick\":447,\"judge\":\"PARTIAL f3209ad3\",\"live_proof\":\"rc"
JSON.parse FAILED -> Error: --attr must be valid JSON
/tmp/db-repro/verify.js)[buggy] parsed attr = "{\"tick\":447,\"judge\":\"PARTIAL f3209ad3\",\"live_proof\":\"rc"
[buggy] JSON.parse FAILED -> Error: --attr must be valid JSON
[fixed] parsed attr = "{\"tick\":447,\"judge\":\"PARTIAL f3209ad3\",\"live_proof\":\"rc=254 unchanged\"}"
[fixed] JSON.parse OK: {"tick":447,"judge":"PARTIAL f3209ad3","live_proof":"rc=254 unchanged"}
Bisection on fixed parser:
OK {"tick":447}
OK {"a":"has spaces"}
OK {"a":"rc=254 unchanged"}
RESULT: buggy=FAIL(expected) fixed=pass
duckbrain remember test-key \
--domain=config \
--namespace=test-ns \
--embedding-text='attr equals regression' \
--attr='{"tick":447,"judge":"PARTIAL f3209ad3","live_proof":"rc=254 unchanged"}' \
--wait
# expected: success; stored attribute round-trips with live_proof === "rc=254 unchanged"
const assert = require('node:assert');
function splitFlagToken(arg) {
const body = arg.slice(2);
const eq = body.indexOf('=');
if (eq === -1) return { name: body, value: true };
return { name: body.slice(0, eq), value: body.slice(eq + 1) };
}
const cases = [
'{"tick":447}',
'{"a":"has spaces"}',
'{"a":"rc=254 unchanged"}',
'{"url":"https://x/y?a=b=c"}',
'{"kv":"k=v&k2=v2"}',
];
for (const json of cases) {
const { name, value } = splitFlagToken(`--attr=${json}`);
assert.strictEqual(name, 'attr');
assert.strictEqual(value, json);
assert.deepStrictEqual(JSON.parse(value), JSON.parse(json));
}
assert.deepStrictEqual(splitFlagToken('--wait'), { name: 'wait', value: true });
console.log('all attr parsing regression checks passed');
Result: all attr parsing regression checks passed.
bin/duckbrain.js — remember flag parser: replace split('=') destructuring with splitFlagToken() (or the indexOf('=') + 1 slice).Note: Because the DuckBrain repo isn't available here, the fix is expressed against the canonical buggy pattern (split('=') destructuring). If bin/duckbrain.js uses a different but equivalent construct (e.g. split('=')[1], split('=').slice(2), or argv.flatMap(a => a.split('='))), the same rule applies: use indexOf('=')/slice so only the first separator is consumed.
# Evidence - Problem class: duckbrain-cli-attr-equals-inside-json-value-fails - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-17T11:02:59.086Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "SYMPTOM: `duckbrain remember <key> --domain=config --namespace=<ns> --embedding-text='...' --attr='{\"tick\":447,\"judge\":\"PARTIAL f3209ad3\",\"live_proof\":\"rc=254 unchanged\"}' --wait` exits with 'Error: --attr must be valid JSON' even though the JSON IS valid and the shell quoting is correct. ROOT CAUSE: the flag parser mangles the attribute value when it contains an '=' - the string value 'rc=254 unchanged' breaks it, so the value the parser hands to JSON.parse is no longer the JSON string. Confirmed by bisection: --attr='{\"tick\":447}' works; --attr='{\"a\":\"has spaces\"}' works; the same payload with 'rc=254' inside a string value fails. WORKAROUND: never put '=' inside a --attr value; write 'rc 254' / 'exitcode 254' instead (or pass the attribute through the HTTP API). Debug method: bisect the flag value down to a minimal JSON object, then reintroduce the suspicious characters one at a time - the error message ('must be valid JSON') is accurate but points at the wrong culprit (it is the parser, not the JSON).", "environment": "DuckBrain CLI (node bin/duckbrain.js, Node 22+, DuckDB-backed), namespace write path", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "duckbrain-cli-attr-equals-inside-json-value-fails", "provider": "openrouter", "solved_at": "2026-09-17T11:02:59.087Z", "version": ""}Diagnosed and verified. The repo isn't present in this sandbox, so I reconstructed the parser and proved both the bug and the fix with runnable harnesses. Solution written to ~/duckbrain-attr-equals-fix.md:
--attr value containing = rejected as "must be valid JSON"duckbrain remember ... --attr='{"live_proof":"rc=254 unchanged"}' fails with Error: --attr must be valid JSON even though the JSON is valid. The JSON was never the problem: the CLI flag parser splits each --flag=value token on every =, discarding everything from the second = onward. The value handed to JSON.parse is a truncated, invalid fragment.
The shell strips the single quotes, so the process sees this single argv token:
--attr={"tick":447,"judge":"PARTIAL f3209ad3","live_proof":"rc=254 unchanged"}
The parser does:
const [name, value] = arg.slice(2).split('=');
split('=') returns one element per =. Destructuring into [name, value] keeps only elements 0 and 1, so the value is cut at the first = inside the JSON string:
name = "attr"
value = '{"tick":447,"judge":"PARTIAL f3209ad3","live_proof":"rc'
JSON.parse('{"tick":447,...,"live_proof":"rc') throws, and the generic catch prints --attr must be valid JSON. The message is accurate for the string it received — it just isn't the string the user passed.
This explains the three-way bisection exactly:
| Payload | Contains = in value? |
Result |
|---|---|---|
--attr='{"tick":447}' |
no | works |
--attr='{"a":"has spaces"}' |
no (spaces stay inside one argv token) | works |
--attr='{"a":"rc=254 unchanged"}' |
yes | fails |
Split only on the first =. Replace the naive split('=') in the remember flag parser (bin/duckbrain.js).
Add a helper:
// Split "--name=value" into its name and its (full) value.
// Only the first '=' is a separator; later '=' characters belong to value.
function splitFlagToken(arg) {
const body = arg.slice(2); // drop leading "--"
const eq = body.indexOf('=');
if (eq === -1) return { name: body, value: true };
return { name: body.slice(0, eq), value: body.slice(eq + 1) };
}
Change the parsing loop:
// BEFORE (buggy: truncates at the first '=' inside the value)
for (const arg of argv) {
if (arg.startsWith('--')) {
const [name, value] = arg.slice(2).split('=');
opts[name] = value;
}
}
// AFTER (correct: everything after the first '=' is the value)
for (const arg of argv) {
if (arg.startsWith('--')) {
const { name, value } = splitFlagToken(arg);
opts[name] = value;
}
}
If there is a dedicated --attr branch instead:
// BEFORE
const value = arg.split('=')[1]; // truncates
// AFTER
const value = arg.slice(arg.indexOf('=') + 1); // keeps the whole JSON
Caller-side workaround (already applied): don't put
=inside an--attrvalue (writerc 254/exitcode 254). The parser fix removes the restriction entirely, so attributes may legitimately contain=, URLs, base64,key=valuepairs, etc.
node -e '
const arg = `--attr={"tick":447,"judge":"PARTIAL f3209ad3","live_proof":"rc=254 unchanged"}`;
const [name, value] = arg.slice(2).split("=");
console.log("value handed to JSON.parse:", JSON.stringify(value));
try { JSON.parse(value); console.log("parsed OK"); }
catch { console.log("JSON.parse FAILED -> Error: --attr must be valid JSON"); }
'
Observed:
value handed to JSON.parse: "{\"tick\":447,\"judge\":\"PARTIAL f3209ad3\",\"live_proof\":\"rc"
JSON.parse FAILED -> Error: --attr must be valid JSON
/tmp/db-repro/verify.js)[buggy] parsed attr = "{\"tick\":447,\"judge\":\"PARTIAL f3209ad3\",\"live_proof\":\"rc"
[buggy] JSON.parse FAILED -> Error: --attr must be valid JSON
[fixed] parsed attr = "{\"tick\":447,\"judge\":\"PARTIAL f3209ad3\",\"live_proof\":\"rc=254 unchanged\"}"
[fixed] JSON.parse OK: {"tick":447,"judge":"PARTIAL f3209ad3","live_proof":"rc=254 unchanged"}
Bisection on fixed parser:
OK {"tick":447}
OK {"a":"has spaces"}
OK {"a":"rc=254 unchanged"}
RESULT: buggy=FAIL(expected) fixed=pass
duckbrain remember test-key \
--domain=config \
--namespace=test-ns \
--embedding-text='attr equals regression' \
--attr='{"tick":447,"judge":"PARTIAL f3209ad3","live_proof":"rc=254 unchanged"}' \
--wait
# expected: success; stored attribute round-trips with live_proof === "rc=254 unchanged"
const assert = require('node:assert');
function splitFlagToken(arg) {
const body = arg.slice(2);
const eq = body.indexOf('=');
if (eq === -1) return { name: body, value: true };
return { name: body.slice(0, eq), value: body.slice(eq + 1) };
}
const cases = [
'{"tick":447}',
'{"a":"has spaces"}',
'{"a":"rc=254 unchanged"}',
'{"url":"https://x/y?a=b=c"}',
'{"kv":"k=v&k2=v2"}',
];
for (const json of cases) {
const { name, value } = splitFlagToken(`--attr=${json}`);
assert.strictEqual(name, 'attr');
assert.strictEqual(value, json);
assert.deepStrictEqual(JSON.parse(value), JSON.parse(json));
}
assert.deepStrictEqual(splitFlagToken('--wait'), { name: 'wait', value: true });
console.log('all attr parsing regression checks passed');
Result: all attr parsing regression checks passed.
bin/duckbrain.js — remember flag parser: replace split('=') destructuring with splitFlagToken() (or the indexOf('=') + 1 slice).Note: Because the DuckBrain repo isn't available here, the fix is expressed against the canonical buggy pattern (split('=') destructuring). If bin/duckbrain.js uses a different but equivalent construct (e.g. split('=')[1], split('=').slice(2), or argv.flatMap(a => a.split('='))), the same rule applies: use indexOf('=')/slice so only the first separator is consumed.
# Evidence - Problem class: duckbrain-cli-attr-equals-inside-json-value-fails - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-17T11:02:59.086Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "SYMPTOM: `duckbrain remember <key> --domain=config --namespace=<ns> --embedding-text='...' --attr='{\"tick\":447,\"judge\":\"PARTIAL f3209ad3\",\"live_proof\":\"rc=254 unchanged\"}' --wait` exits with 'Error: --attr must be valid JSON' even though the JSON IS valid and the shell quoting is correct. ROOT CAUSE: the flag parser mangles the attribute value when it contains an '=' - the string value 'rc=254 unchanged' breaks it, so the value the parser hands to JSON.parse is no longer the JSON string. Confirmed by bisection: --attr='{\"tick\":447}' works; --attr='{\"a\":\"has spaces\"}' works; the same payload with 'rc=254' inside a string value fails. WORKAROUND: never put '=' inside a --attr value; write 'rc 254' / 'exitcode 254' instead (or pass the attribute through the HTTP API). Debug method: bisect the flag value down to a minimal JSON object, then reintroduce the suspicious characters one at a time - the error message ('must be valid JSON') is accurate but points at the wrong culprit (it is the parser, not the JSON).", "environment": "DuckBrain CLI (node bin/duckbrain.js, Node 22+, DuckDB-backed), namespace write path", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "duckbrain-cli-attr-equals-inside-json-value-fails", "provider": "openrouter", "solved_at": "2026-09-17T11:02:59.087Z", "version": ""}