# 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; see `CHANGELOG.md` for corrections:
>
> - **BUG-2 location:** the empty-body crash site is the `else` branch at
>   `src/commands/serve-http.ts:1594-1597` (`Buffer.from(JSON.stringify(req.body), 'utf8')`
>   throws when `req.body === undefined` because `JSON.stringify(undefined) === undefined`),
>   not line 1508 as originally reported. The empty-Buffer guard at `:1600`
>   correctly fires for empty Buffers but never reaches the undefined case.
>
> - **WARN-5 root cause:** `gbrain capture --help` is minimal because
>   `capture` is missing from the `CLI_ONLY_SELF_HELP` set at `src/cli.ts:34-53`.
>   The detailed `HELP` constant at `src/commands/capture.ts:90-113` is
>   correct and comprehensive but unreachable; the dispatcher's generic
>   short-circuit at `:95` fires `printCliOnlyHelp(command)` first.
>   `brainstorm` and `lsd` are 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:**
```bash
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:

```yaml
---
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:**
```bash
# 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:
```typescript
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

```bash
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:

```json
{ "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 delete` works with
  72h recovery window.
- **`--type` flag** — `note`, `idea`, `observation` all work correctly.

---

## 💡 Suggestions

1. **`capture --help` parity** — give it the same quality help text as
   `brainstorm --help`. Document every flag.

2. **Main help completeness** — add `capture`, `brainstorm`, and `lsd` to
   the `gbrain --help` command listing. These are user-facing features that
   can't be discovered.

3. **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.

4. **Provenance write-through** — `capture` already knows it's
   `source_kind: "capture-cli"`. Pass this through to `put_page` so the
   DB columns are populated.

5. **Binary file guard** — reject or warn on non-text input in
   `capture --file`. Check file content or extension before proceeding.

6. **`--source` error UX** — catch the FK violation and print a
   human-friendly hint about registering sources.
