v0.38 smoke test report
docs/v0.38-smoke-test-report.md
v0.38.0.0 Smoke Test Report
Editor's Note (v0.39.3.0): This report was contributed verbatim from PR #1299 (
garrytan-agents). Two findings were re-diagnosed during the v0.39.3.0 wave; seeCHANGELOG.mdfor corrections:
BUG-2 location: the empty-body crash site is the
elsebranch atsrc/commands/serve-http.ts:1594-1597(Buffer.from(JSON.stringify(req.body), 'utf8')throws whenreq.body === undefinedbecauseJSON.stringify(undefined) === undefined), not line 1508 as originally reported. The empty-Buffer guard at:1600correctly fires for empty Buffers but never reaches the undefined case.WARN-5 root cause:
gbrain capture --helpis minimal becausecaptureis missing from theCLI_ONLY_SELF_HELPset atsrc/cli.ts:34-53. The detailedHELPconstant atsrc/commands/capture.ts:90-113is correct and comprehensive but unreachable; the dispatcher's generic short-circuit at:95firesprintCliOnlyHelp(command)first.brainstormandlsdare in the self-help set, which is why their help works.All 2 bugs and 10 warnings are addressed in v0.39.3.0. The report below is preserved as the historical record of what production looked like on 2026-05-22.
Production smoke test of the v0.38.0.0 ingestion cathedral release on a live Postgres-backed server (Supabase, pgvector, PgBouncer transaction mode).
Test date: 2026-05-22 Server: MCP HTTP server v0.38.0.0 on port 3131, Postgres engine Prior version: v0.37.9.0
Summary
| Category | Pass | Warn | Fail |
|---|---|---|---|
gbrain capture — basic | 7 | 0 | 0 |
gbrain capture — edge cases | 3 | 3 | 1 |
| Dedup behavior | 0 | 2 | 0 |
| POST /ingest webhook | 5 | 1 | 1 |
| Provenance columns | 2 | 1 | 0 |
brainstorm / lsd | 2 | 1 | 0 |
| CLI help & discoverability | 1 | 2 | 0 |
| MCP server health | 1 | 0 | 0 |
| Total | 21 | 10 | 2 |
🐛 Bugs (2)
BUG-1: capture --file doubles frontmatter on files with existing frontmatter
Severity: Medium Repro:
cat > /tmp/test.md << 'EOF'
---
title: Pre-existing Title
tags: [test, frontmatter]
---
# Pre-existing content
This file already has frontmatter.
EOF
gbrain capture --file /tmp/test.md
gbrain get inbox/2026-05-22-XXXXXX
Observed: The page on disk gets TWO frontmatter blocks. The outer block
has title: '---' (it parsed the inner --- delimiter as the title), and
the original frontmatter is preserved verbatim inside the body:
---
type: note
title: '---'
captured_at: '2026-05-22T16:06:11.334Z'
captured_via: capture-cli
ingested_via: put_page
ingested_at: '2026-05-22T16:06:13.038Z'
source_kind: put_page
---
---
title: Pre-existing Title
tags: [test, frontmatter]
---
# Pre-existing content
Expected: buildContent should detect existing frontmatter (the commit
message says it has a "looks like markdown" heuristic for first-line heading
or frontmatter delimiter) and not double-wrap. The inner frontmatter fields
should merge with capture's fields.
Location: src/commands/brainstorm.ts → buildContent function (shared
with capture).
BUG-2: POST /ingest crashes with unhandled TypeError on empty body
Severity: Medium Repro:
# With valid bearer token:
curl -X POST localhost:3131/ingest \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: text/plain"
Observed: Returns a 500 HTML error page with stack trace:
TypeError: The first argument must be of type string, Buffer, ArrayBuffer,
Array, or Array-like Object. Received undefined
at serve-http.ts:1508:23
Expected: The route already has an empty_body check (documented in the
code and tested in the E2E suite), but the body-parser middleware for
/ingest uses express.raw() which returns undefined for an empty POST
(no Content-Length, no body). The empty_body guard fires AFTER the
computeContentHash(body) call, which crashes on undefined.
Fix: Move the null/undefined/empty-buffer check before the content-hash computation, or add a guard at the top of the route handler:
if (!req.body || req.body.length === 0) {
return res.status(400).json({ error: 'empty_body', message: '...' });
}
⚠️ Warnings (10)
WARN-1: Dedup does not actually deduplicate identical captures
Observed: Capturing identical text twice produces the same slug but
different content_hash values:
Run 1: slug=inbox/2026-05-22-3d5b671a, hash=1faf3166...
Run 2: slug=inbox/2026-05-22-3d5b671a, hash=f6ee8098...
Root cause: The content hash includes the full serialized page with
frontmatter, and captured_at changes between runs (it's timestamped).
The slug is deterministic (derived from content text), so it correctly
maps to the same page — but the hash changes every time.
Impact: The "24h content-hash LRU dedup" in the daemon layer won't
catch duplicate captures because the hash differs. The put_page
upsert catches it at the slug level (overwrites), so no duplicate pages
are created — but the content_hash-based dedup layer is effectively
bypassed for CLI captures.
Suggestion: Compute content_hash from the user's input text before
adding frontmatter, or exclude captured_at from the hash computation.
WARN-2: Same text with different --type flags overwrites the previous capture
Same slug is generated for identical text regardless of --type. The
second capture with --type observation silently overwrites the first
with --type idea. This is correct behavior (slug = content hash), but
may surprise users who expect type changes to produce distinct pages.
WARN-3: --source flag crashes with raw FK violation
gbrain capture: put_page failed: insert or update on table "pages" violates
foreign key constraint "pages_source_id_fk"
The error message exposes a raw Postgres FK violation. Users have no way to know what to do. The capture command should catch this error and print a human-friendly message:
Error: source 'my-source' is not registered. Register it first:
gbrain sources add my-source --path /path/to/source
WARN-4: facts:absorb connection error after every capture
Every capture logs:
[facts:absorb] failed to log gateway_error for inbox/...: No database
connection: connect() has not been called. Fix: Run gbrain init --supabase
Non-fatal (exit 0), but noisy. The facts subsystem tries to open a separate connection that the CLI capture path doesn't initialize.
WARN-5: capture --help is minimal — doesn't show flags
$ gbrain capture --help
Usage: gbrain capture
gbrain capture - run gbrain --help for the full command list.
Compare with brainstorm --help which has a full Options section,
examples, and cost info. The capture help should document --type,
--file, --source, --json, --stdin, --slug, etc.
WARN-6: capture, brainstorm, lsd missing from gbrain --help
None of these three commands appear in the main help text. They work when
invoked directly, but users can't discover them from the help output.
gbrain --help lists every other command (get, put, search, etc.) but
the v0.37/v0.38 commands are absent.
WARN-7: Binary file capture succeeds silently
head -c 256 /dev/urandom > /tmp/binary.bin
gbrain capture --file /tmp/binary.bin
This succeeds, creating a page with binary garbage as content. Should either reject non-text files or at minimum warn.
WARN-8: Provenance columns not populated by capture
The capture CLI reports "source_kind": "capture-cli" in its JSON output,
but the actual database row has:
{ "source_id": "default", "source_uri": null, "source_kind": null }
The provenance fields from capture's output don't round-trip into the
put_page call that persists the page.
WARN-9: Admin register-client ignores scope parameter
Registering a client via /admin/api/register-client with
"scope": "read write" creates a client with scope "read" only.
The admin endpoint appears to ignore or default the scope field. Required
manual DB update to get write scope for webhook testing.
WARN-10: brainstorm / lsd may hang or timeout on PgBouncer
In transaction-mode PgBouncer environments, brainstorm consistently
times out after the cost estimate with canceling statement due to statement timeout. The hybrid search + domain-bank phase appears to
hit PgBouncer's statement timeout. This may be environmental, but the
command should handle the timeout gracefully and report what failed
rather than silently producing no output.
✅ What Works Well
gbrain capture "text"— works perfectly for the simple case. Frontmatter stamps, JSON output, disk write, immediate searchability.gbrain capture --file— works for plain text and simple markdown (frontmatter doubling bug only affects files with existing frontmatter).gbrain capture ""and no-args — clean error messages:"provide content positionally, --file PATH, or --stdin".gbrain capture --file /nonexistent— clean ENOENT error.- Unicode/emoji —
gbrain capture "测试 🧠🔥 émojis"works perfectly. - Long text — 2500+ character capture works, correctly produces 2 chunks.
- POST /ingest auth gate — properly rejects missing auth (401), invalid tokens (401), and insufficient scope (403).
- POST /ingest content-type validation — properly rejects image/png and application/pdf with a helpful error pointing to skillpack processors.
- POST /ingest accepted types — text/plain, text/markdown, text/html, application/json all accepted and queued.
- X-Gbrain-Source-Id header — custom source ID flows through correctly.
- MCP health endpoint — returns clean JSON with version and engine.
- OAuth client_credentials flow — works correctly once the client has proper scope.
- Content-hash dedup on webhook — verified via response
content_hash. - Migration v81 — provenance columns added cleanly, nullable, no disruption to existing pages. 290K+ existing pages unaffected.
brainstorm --help/lsd --help— excellent help text with examples, cost estimates, and cross-references.- Soft delete + recovery — cleanup via
gbrain deleteworks with 72h recovery window. --typeflag —note,idea,observationall work correctly.
💡 Suggestions
-
capture --helpparity — give it the same quality help text asbrainstorm --help. Document every flag. -
Main help completeness — add
capture,brainstorm, andlsdto thegbrain --helpcommand listing. These are user-facing features that can't be discovered. -
Content-hash stability — consider computing the dedup hash from user input text only (before frontmatter injection) so identical captures are properly deduped at the daemon layer.
-
Provenance write-through —
capturealready knows it'ssource_kind: "capture-cli". Pass this through toput_pageso the DB columns are populated. -
Binary file guard — reject or warn on non-text input in
capture --file. Check file content or extension before proceeding. -
--sourceerror UX — catch the FK violation and print a human-friendly hint about registering sources.