> ## Documentation Index
> Fetch the complete documentation index at: https://docs.modusbrain.com/llms.txt
> Use this file to discover all available pages before exploring further.

# V0.38 smoke test report

# v0.38.0.0 Smoke Test Report

> **Editor's Note (v0.39.3.0):** This report was contributed verbatim from
> PR #1299 (`thebuildceo-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:** `modusbrain 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  |
| --------------------------------- | ------ | ------ | ----- |
| `modusbrain capture` — basic      | 7      | 0      | 0     |
| `modusbrain 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 theme={null}
cat > /tmp/test.md << 'EOF'
---
title: Pre-existing Title
tags: [test, frontmatter]
---

# Pre-existing content

This file already has frontmatter.
EOF

modusbrain capture --file /tmp/test.md
modusbrain 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 theme={null}
---
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 theme={null}
# 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 theme={null}
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

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

```
$ modusbrain capture --help
Usage: modusbrain capture

modusbrain capture - run modusbrain --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 `modusbrain --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.
`modusbrain --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 theme={null}
head -c 256 /dev/urandom > /tmp/binary.bin
modusbrain 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 theme={null}
{ "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

* **`modusbrain capture "text"`** — works perfectly for the simple case.
  Frontmatter stamps, JSON output, disk write, immediate searchability.
* **`modusbrain capture --file`** — works for plain text and simple markdown
  (frontmatter doubling bug only affects files with existing frontmatter).
* **`modusbrain capture ""`** and no-args — clean error messages:
  `"provide content positionally, --file PATH, or --stdin"`.
* **`modusbrain capture --file /nonexistent`** — clean ENOENT error.
* **Unicode/emoji** — `modusbrain 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-Modusbrain-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 `modusbrain 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 `modusbrain --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.
