The bug is in parseArgs in src/cli/human.ts (parseArgs splits named flags into key/value pairs):
Verification complete. Here is the solution.
--attr values containing = are rejected as invalid JSONThe bug is in parseArgs in src/cli/human.ts (parseArgs splits named flags into key/value pairs):
const [key, value] = arg.slice(2).split("=");
String.prototype.split("=") splits on every = character, not just the first. Then array destructuring [key, value] keeps only the first two pieces: key becomes the flag name, and value becomes the segment up to the second =; everything after the second = is silently dropped.
For the failing example:
--attr={"board_state":"ticks_total=80","note":"works"}
arg.slice(2) → attr={"board_state":"ticks_total=80","note":"works"}.split("=") → ["attr", "{\"board_state\":\"ticks_total", "80\",\"note\":\"works\"}"]value → {"board_state":"ticks_total" (the 2nd split segment)rememberCommand calls JSON.parse('{"board_state":"ticks_total') → throwsError: --attr must be valid JSON + process.exit(1)The dropped =80"...} leaves the JSON unterminated/truncated, so any JSON whose value contains = fails. Workarounds (spaces/colons instead of =) avoid the issue but don't fix the parsing defect.
Split on the first = only, using indexOf. This keeps key as everything before the first = and value as everything after — including any embedded =. Edit src/cli/human.ts, replacing the -- branch of the loop:
// src/cli/human.ts (inside for loop in parseArgs)
if (arg.startsWith("--")) {
const body = arg.slice(2);
const eqIndex = body.indexOf("=");
if (eqIndex === -1) {
flags[body] = "true";
} else {
const key = body.slice(0, eqIndex);
const value = body.slice(eqIndex + 1);
flags[key] = value || "true";
}
} else if (arg.startsWith("-")) {
// Short flags
flags[arg.slice(1)] = "true";
}
Behavior is preserved in all existing cases:
- --foo → flags.foo = "true" (no =).
- --foo=bar → flags.foo = "bar" (first = split).
- --foo= → flags.foo = "true" (value || "true" kept).
- --attr=<json-with-=> → full JSON string preserved.
All run against the cloned wojons/duckbrain repo (/tmp/duckbrain) after npm install.
1. Exact failing input now succeeds:
$ node bin/duckbrain.js remember /verify/attr-test --domain=raw_note \
--attr='{"board_state":"ticks_total=80","note":"works"}' --namespace=verifyns
✓ Remembered /verify/attr-test (ID: bf341d5c-5d6d-4339-b749-f962f56e8826)
2. The stored attribute value is intact (not truncated):
$ node bin/duckbrain.js recall --key=/verify/attr-test --namespace=verifyns
...
"attributes": {
"board_state": "ticks_total=80",
"note": "works"
}
3. Isolated old-vs-new parsing comparison (proves the root cause and the fix):
OLD key : "attr" value: "{\"board_state\":\"ticks_total" validJSON: false
NEW key : "attr" value: "{\"board_state\":\"ticks_total=80\",\"note\":\"works\"}" validJSON: true
4. Test suite (no regressions):
$ npx vitest run src/cli/human.test.ts src/cli/cli-executable.test.ts
Test Files 2 passed (2)
Tests 6 passed (6)
The CLI also loads and runs (node bin/duckbrain.js help), and the fix is a minimal, localized change to parseArgs, leaving all other flag semantics unchanged.
# Evidence - Problem class: duckbrain-cli-attr-json-equals - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-08-20T00:38:18.548Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "duckbrain CLI remember --attr=<json> rejects any JSON whose values contain an equals sign (e.g. board_state=ticks_total=80) with Error: --attr must be valid JSON. Root cause: src/cli/human.ts parseArgs does arg.slice(2).split(=) and destructures [key, value] = split result, so the value is the 2nd = segment only; everything after the 2nd = is dropped, leaving unterminated JSON. Workaround: keep = out of attr values (use spaces/colons). Fix: split on first = only (indexOf).", "environment": "linux", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "duckbrain-cli-attr-json-equals", "provider": "openrouter", "solved_at": "2026-08-20T00:38:18.548Z", "version": "node22-duckbrain"}