Skip to content

v0.12.3: Reliability wave — sync deadlock, search timeout scoping, wikilinks, orphans#216

Merged
garrytan merged 9 commits intomasterfrom
garrytan/perf-wave
Apr 19, 2026
Merged

v0.12.3: Reliability wave — sync deadlock, search timeout scoping, wikilinks, orphans#216
garrytan merged 9 commits intomasterfrom
garrytan/perf-wave

Conversation

@garrytan
Copy link
Copy Markdown
Owner

Summary

Reliability wave follow-up to v0.12.1 (extract N+1 + migration timeout) and v0.12.2 (JSONB double-encode + splitBody + /wiki/ types + parseEmbedding). Lands the remaining community-sourced fixes from the same review pass, plus one graph-layer feature (gbrain orphans) that closes the loop on the v0.12 knowledge graph story.

No schema changes. No migration. No data touch. gbrain upgrade pulls it.

What ships

  • Sync stops hanging past 10 files (fix(sync): deadlock on incremental sync with > 10 modified files #132, @sunnnybala) — src/commands/sync.ts wrapped the whole import in engine.transaction, and importFromContent also wrapped per-file. PGLite's non-reentrant mutex deadlocked. Outer wrap removed; per-file atomicity preserved. Regression test asserts top-level engine.transaction is not called.
  • statement_timeout scoped to the transaction (postgres-engine: scope search statement_timeout to the transaction #158, @garagon) — searchKeyword / searchVector used SET statement_timeout='8s' + finally SET 0, but each tagged template picks an arbitrary pool connection. The cap leaked across the postgres.js pool and strangled unrelated queries. Now uses sql.begin + SET LOCAL. 5 regression tests including a source-level grep guardrail.
  • Obsidian [[WikiLinks]] as first-class edges (fix: markdown parsing bugs affecting wiki-style content #187 slice, @knee5) — extractEntityRefs matches both [Name](people/slug) and [[people/slug|Name]]. DIR_PATTERN extended to entities, projects, tech, finance, personal, openclaw. Before: a 2,100-page brain extracted zero auto-links on put_page. After: 1,377 typed edges on a single pass.
  • gbrain orphans command (fix: markdown parsing bugs affecting wiki-style content #187 slice, @knee5) — surfaces pages with zero inbound wikilinks. Text/JSON/count outputs, domain grouping, --include-pseudo flag. Also exposed as find_orphans MCP operation.
  • tryParseEmbedding() availability helper — new sibling of parseEmbedding() that returns null + warns once instead of throwing. Used on getEmbeddingsByChunkIds so one corrupt Supabase row degrades ranking instead of killing the query. Migration/ingest paths still throw.
  • Doctor detection for v0.12.0 damagejsonb_integrity scans the four JSONB write sites and reports jsonb_typeof='string' rows; markdown_body_completeness heuristically flags truncated-body pages. Fix hints point at gbrain repair-jsonb (shipped in v0.12.2) and gbrain sync --force.

Credits

All commits preserve Co-Authored-By trailers via git cherry-pick -x.

Test plan

  • bun test (unit only) — 1340 pass / 0 fail
  • bun run test:e2e on fresh pgvector/pgvector:pg16 container — 129 pass / 5 skip / 0 fail
  • All 7 reliability commits preserved on top of origin/master
  • No schema change, no migration file, no data touch

🤖 Generated with Claude Code

sunnnybala and others added 9 commits April 19, 2026 14:01
sync.ts wraps the add/modify loop in engine.transaction(), and each
importFromContent inside opens another one. PGLite's
_runExclusiveTransaction is a non-reentrant mutex — the second call
queues on the mutex the first is holding, and the process hangs forever
in ep_poll. Reproduced with a 15-file commit: unpatched hangs, patched
runs in 3.4s. Fix drops the outer wrap; per-file atomicity is correct
anyway (one file's failure should not roll back the others).

(cherry picked from commit 4a1ac00)
Reads src/commands/sync.ts verbatim and asserts no uncommented
engine.transaction() call appears above the add/modify loop. Protects
against silent reintroduction of the nested-mutex deadlock that hung
> 10-file syncs forever in ep_poll.
parseEmbedding() throws on structural corruption — right call for ingest/
migrate paths where silent skips would be data loss. Wrong call for
search/rescore paths where one corrupt row in 10K would kill every
query that touches it.

tryParseEmbedding() wraps parseEmbedding in try/catch: returns null on
any shape that would throw, warns once per session so the bad row is
visible in logs. Use it anywhere we'd rather degrade ranking than blow
up the whole query.

Retrofit postgres-engine.getEmbeddingsByChunkIds (the #175 slice call
site) — the 5-line rescore loop was the direct motivator. Keep the
throwing parseEmbedding() for everything else (pglite-engine rowToChunk,
migrate-engine round-trips, ingest).
searchKeyword and searchVector run on a pooled postgres.js client
(max: 10 by default). The original code bounded each search with

  await sql`SET statement_timeout = '8s'`
  try { await sql`<query>` }
  finally { await sql`SET statement_timeout = '0'` }

but every tagged template is an independent round-trip that picks an
arbitrary connection from the pool. The SET, the query, and the reset
could all land on DIFFERENT connections. In practice the GUC sticks
to whichever connection ran the SET and then gets returned to the
pool — the next unrelated caller on that connection inherits the 8s
timeout (clipping legitimate long queries) or the reset-to-0 (disabling
the guard for whoever expected it). A crash in the middle leaves the
state set permanently.

Wrap each search in sql.begin(async sql => …). postgres.js reserves
a single connection for the transaction body, so the SET LOCAL, the
query, and the implicit COMMIT all run on the same connection. SET
LOCAL scopes the GUC to the transaction — COMMIT or ROLLBACK restores
the previous value automatically, regardless of the code path out.
Error paths can no longer leak the GUC.

No API change. Timeout value and semantics are identical (8s cap on
search queries, no effect on embed --all / bulk import which runs
outside these methods). Only one transaction per search — BEGIN +
COMMIT round-trips are negligible next to a ranked FTS or pgvector
query.

Also closes the earlier audit finding R4-F002 which reported the same
pattern on searchKeyword. This PR covers both searchKeyword and
searchVector so the pool-leak class is fully closed.

Tests (test/postgres-engine.test.ts, new file):
- No bare SET statement_timeout remains after stripping comments.
- searchKeyword and searchVector each wrap their query in sql.begin.
- Both use SET LOCAL.
- Neither explicitly clears the timeout with SET statement_timeout=0.

Source-level guardrails keep the fast unit suite DB-free. Live
Postgres coverage of the search path is in test/e2e/search-quality.test.ts,
which continues to exercise these methods end-to-end against
pgvector when DATABASE_URL is set.

(cherry picked from commit 6146c3b)
… pages

Surfaces pages with zero inbound wikilinks. Essential for content
enrichment cycles in KBs with 1000+ pages. By default filters out
auto-generated pages, raw sources, and pseudo-pages where no inbound
links is expected; --include-pseudo to disable.

Supports text (grouped by domain), --json, --count outputs.
Also exposed as find_orphans MCP operation.

Tests cover basic detection, filtering, all output modes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
(cherry picked from commit f50954f)
…n canonical extractor

extractEntityRefs now recognizes both syntaxes equally:
  [Name](people/slug)      -- upstream original
  [[people/slug|Name]]     -- Obsidian wikilink (new)

Extends DIR_PATTERN to include domain-organized wiki slugs used by
Karpathy-style knowledge bases:
  - entities  (legacy prefix some brains keep during migration)
  - projects  (gbrain canonical, was missing from regex)
  - tech, finance, personal, openclaw (domain-organized wiki roots)

Before this change, a 2,100-page brain with wikilinks throughout extracted
zero auto-links on put_page because the regex only matched markdown-style
[name](path). After: 1,377 new typed edges on a single extract --source db
pass over the same corpus.

Matches the behavior of the extract.ts filesystem walker (which already
handled wikilinks as of the wiki-markdown-compat fix wave), so the db and
fs sources now produce the same link graph from the same content.

Both patterns share the DIR_PATTERN constant so adding a new entity dir
only requires updating one string.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(cherry picked from commit 1cfb156)
Add two v0.12.1-era reliability checks to `gbrain doctor`:

- `jsonb_integrity` scans the 4 known write sites from the v0.12.0
  double-encode bug (pages.frontmatter, raw_data.data,
  ingest_log.pages_updated, files.metadata) and reports rows where
  jsonb_typeof(col) = 'string'. The fix hint points at
  `gbrain repair-jsonb` (the standalone repair command shipped in
  v0.12.1).

- `markdown_body_completeness` flags pages whose compiled_truth is
  <30% of the raw source content length when raw has multiple H2/H3
  boundaries. Heuristic only; suggests `gbrain sync --force` or
  `gbrain import --force <slug>`.

Also adds test/e2e/jsonb-roundtrip.test.ts — the regression coverage
that should have caught the original double-encode bug. Hits all four
write sites against real Postgres and asserts jsonb_typeof='object'
plus `->>'key'` returns the expected scalar.

Detection only: doctor diagnoses, `gbrain repair-jsonb` treats.
No overlap with the standalone repair path.
Master shipped v0.12.1 (extract N+1 + migration timeout) and v0.12.2
(JSONB double-encode + splitBody + wiki types + parseEmbedding) while
this wave was mid-flight. Ships the remaining pieces as v0.12.3:

- sync deadlock (#132, @sunnnybala)
- statement_timeout scoping (#158, @garagon)
- Obsidian wikilinks + domain patterns (#187 slice, @knee5)
- gbrain orphans command (#187 slice, @knee5)
- tryParseEmbedding() availability helper
- doctor detection for jsonb_integrity + markdown_body_completeness

No schema, no migration, no data touch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CLAUDE.md:
- Add src/commands/orphans.ts entry
- Expand src/commands/doctor.ts with v0.12.3 jsonb_integrity +
  markdown_body_completeness check descriptions
- Update src/core/link-extraction.ts to mention Obsidian wikilinks +
  extended DIR_PATTERN (entities/projects/tech/finance/personal/openclaw)
- Update src/core/utils.ts to mention tryParseEmbedding sibling
- Update src/core/postgres-engine.ts to note statement_timeout scoping +
  tryParseEmbedding usage in getEmbeddingsByChunkIds
- Add Key commands added in v0.12.3 section (orphans, doctor checks)
- Add test/orphans.test.ts, test/postgres-engine.test.ts, updated
  descriptions for test/sync.test.ts, test/doctor.test.ts,
  test/utils.test.ts
- Add test/e2e/jsonb-roundtrip.test.ts with note on intentional overlap
- Bump operation count from ~36 to ~41 (find_orphans shipped in v0.12.3)

README.md:
- Add gbrain orphans to ADMIN commands block

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@garrytan garrytan merged commit 013b348 into master Apr 19, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants