fix(install): mark src/cli.ts executable so bin symlink works after bun install (closes #388)#525
Closed
notjbg wants to merge 1 commit into
Closed
fix(install): mark src/cli.ts executable so bin symlink works after bun install (closes #388)#525notjbg wants to merge 1 commit into
notjbg wants to merge 1 commit into
Conversation
bun install symlinks ~/.bun/bin/gbrain -> src/cli.ts via the package.json "bin" map. With src/cli.ts at mode 100644 the symlink target isn't executable, so the postinstall hook (gbrain apply-migrations) fails with 'Permission denied' and migrations are silently skipped. Flip the mode to 100755 — the file already has a #!/usr/bin/env bun shebang and is intended to be directly executable. Closes garrytan#388
garrytan
pushed a commit
that referenced
this pull request
Apr 30, 2026
JSON.stringify(input) + ::jsonb cast produced a jsonb string value
instead of a jsonb object. The postgres library's unsafe() with a
raw object + ::jsonb correctly stores a jsonb object.
This caused collectChildPutPageSlugs to return 0 results (can't
extract ->>'slug' from a jsonb string), making dream synthesize
report '0 pages written' even though subagents successfully wrote
16 pages to the database.
Fix: pass objects as-is to executeRaw, let the postgres driver
handle serialization. Non-object values wrapped in {_raw: ...}
as a safety fallback.
garrytan
pushed a commit
that referenced
this pull request
Apr 30, 2026
This reverts commit 80b3909.
garrytan
pushed a commit
that referenced
this pull request
Apr 30, 2026
JSON.stringify(input) + ::jsonb cast produced a jsonb string value
instead of a jsonb object. The postgres library's unsafe() with a
raw object + ::jsonb correctly stores a jsonb object.
This caused collectChildPutPageSlugs to return 0 results (can't
extract ->>'slug' from a jsonb string), making dream synthesize
report '0 pages written' even though subagents successfully wrote
16 pages to the database.
Fix: pass objects as-is to executeRaw, let the postgres driver
handle serialization. Non-object values wrapped in {_raw: ...}
as a safety fallback.
5 tasks
vinsew
added a commit
to vinsew/gbrain
that referenced
this pull request
May 11, 2026
…nb + doctor
The four `subagent_*` jsonb writers in `src/core/minions/handlers/subagent.ts`
(persistMessage, persistToolExecPending, persistToolExecComplete,
persistToolExecFailed) used `engine.executeRaw` with `JSON.stringify(value)`
plus `$N::jsonb` cast. On the postgres-engine path that goes through
postgres.js's `unsafe()`, that combination produces a jsonb string scalar
instead of an object — exactly the v0.12.0 double-encode shape, on a second
set of tables.
Verified empirically (probe inside this branch, not committed):
P1 JSON.stringify(obj) + $::jsonb → jsonb_typeof='string' (broken)
P3 raw object + $::jsonb → jsonb_typeof='object' (correct)
P4 sql.json(obj) via unsafe → jsonb_typeof='object' (correct)
The four call sites are changed to pass the raw value. postgres.js v3
auto-encodes objects/arrays for jsonb columns (already what queue.ts:233
relies on for `minion_jobs.data`, never broken). PGLite handles raw values
identically (verified — all three patterns produce object jsonb on PGLite).
Existing data on the user's brain at the time of fix:
subagent_messages.content_blocks: 40/40 corrupt
subagent_tool_executions.input: 39/39 corrupt
subagent_tool_executions.output: 39/39 corrupt
All 118 rows repaired by `gbrain repair-jsonb` after extending TARGETS.
Symptom this resolves: dream synthesize's orchestrator
(`collectChildPutPageSlugs` in src/core/cycle/synthesize.ts:459) queries
`input->>'slug'` to gather slugs the subagent wrote; on string-typed jsonb
that operator returns NULL, so `pages_written` reports 0 even when child
subagents successfully wrote pages. The reverse-write step that mirrors
DB → markdown is then skipped, leaving real synthesized pages stranded
in the DB. After this fix, the orchestrator picks up slugs correctly and
the full dream cycle closes the loop.
Companion changes:
- `repair-jsonb` extended from 5 → 8 columns (subagent_messages.content
_blocks, subagent_tool_executions.{input,output}); guards each target
with `to_regclass()` so pre-v0.15 brains that lack the subagent_*
tables don't throw at upgrade time.
- `doctor`'s `jsonb_integrity` check extended to the same 8 columns
with the same guard. Any future user hitting this bug from upstream
master gets a clear "X rows double-encoded ... Fix: gbrain
repair-jsonb" warning instead of silent dream failures.
- `repair-jsonb.ts` header comment retracted: the prior claim that
parameterized `$N::jsonb` was always safe was wrong; safety came
from queue.ts/etc. passing raw objects, not from the binding form.
Upstream history: PR garrytan#525 was a chmod fix mis-attributed by local commits;
the real outstanding upstream PR is garrytan#539 (zombie lock + post-hoc data
repair via UPDATE) which does NOT patch the write path. Master HEAD still
ships the buggy form. This patch is upstream-worthy as a follow-up to
Tests: 245 pass across minions, subagent-handler, cycle-synthesize,
cycle-patterns, repair-jsonb, migrations-v0_12_2, and doctor. typecheck
clean.
vinsew
added a commit
to vinsew/gbrain
that referenced
this pull request
May 23, 2026
…nb + doctor
The four `subagent_*` jsonb writers in `src/core/minions/handlers/subagent.ts`
(persistMessage, persistToolExecPending, persistToolExecComplete,
persistToolExecFailed) used `engine.executeRaw` with `JSON.stringify(value)`
plus `$N::jsonb` cast. On the postgres-engine path that goes through
postgres.js's `unsafe()`, that combination produces a jsonb string scalar
instead of an object — exactly the v0.12.0 double-encode shape, on a second
set of tables.
Verified empirically (probe inside this branch, not committed):
P1 JSON.stringify(obj) + $::jsonb → jsonb_typeof='string' (broken)
P3 raw object + $::jsonb → jsonb_typeof='object' (correct)
P4 sql.json(obj) via unsafe → jsonb_typeof='object' (correct)
The four call sites are changed to pass the raw value. postgres.js v3
auto-encodes objects/arrays for jsonb columns (already what queue.ts:233
relies on for `minion_jobs.data`, never broken). PGLite handles raw values
identically (verified — all three patterns produce object jsonb on PGLite).
Existing data on the user's brain at the time of fix:
subagent_messages.content_blocks: 40/40 corrupt
subagent_tool_executions.input: 39/39 corrupt
subagent_tool_executions.output: 39/39 corrupt
All 118 rows repaired by `gbrain repair-jsonb` after extending TARGETS.
Symptom this resolves: dream synthesize's orchestrator
(`collectChildPutPageSlugs` in src/core/cycle/synthesize.ts:459) queries
`input->>'slug'` to gather slugs the subagent wrote; on string-typed jsonb
that operator returns NULL, so `pages_written` reports 0 even when child
subagents successfully wrote pages. The reverse-write step that mirrors
DB → markdown is then skipped, leaving real synthesized pages stranded
in the DB. After this fix, the orchestrator picks up slugs correctly and
the full dream cycle closes the loop.
Companion changes:
- `repair-jsonb` extended from 5 → 8 columns (subagent_messages.content
_blocks, subagent_tool_executions.{input,output}); guards each target
with `to_regclass()` so pre-v0.15 brains that lack the subagent_*
tables don't throw at upgrade time.
- `doctor`'s `jsonb_integrity` check extended to the same 8 columns
with the same guard. Any future user hitting this bug from upstream
master gets a clear "X rows double-encoded ... Fix: gbrain
repair-jsonb" warning instead of silent dream failures.
- `repair-jsonb.ts` header comment retracted: the prior claim that
parameterized `$N::jsonb` was always safe was wrong; safety came
from queue.ts/etc. passing raw objects, not from the binding form.
Upstream history: PR garrytan#525 was a chmod fix mis-attributed by local commits;
the real outstanding upstream PR is garrytan#539 (zombie lock + post-hoc data
repair via UPDATE) which does NOT patch the write path. Master HEAD still
ships the buggy form. This patch is upstream-worthy as a follow-up to
Tests: 245 pass across minions, subagent-handler, cycle-synthesize,
cycle-patterns, repair-jsonb, migrations-v0_12_2, and doctor. typecheck
clean.
Owner
vinsew
added a commit
to vinsew/gbrain
that referenced
this pull request
Jun 1, 2026
…nb + doctor
The four `subagent_*` jsonb writers in `src/core/minions/handlers/subagent.ts`
(persistMessage, persistToolExecPending, persistToolExecComplete,
persistToolExecFailed) used `engine.executeRaw` with `JSON.stringify(value)`
plus `$N::jsonb` cast. On the postgres-engine path that goes through
postgres.js's `unsafe()`, that combination produces a jsonb string scalar
instead of an object — exactly the v0.12.0 double-encode shape, on a second
set of tables.
Verified empirically (probe inside this branch, not committed):
P1 JSON.stringify(obj) + $::jsonb → jsonb_typeof='string' (broken)
P3 raw object + $::jsonb → jsonb_typeof='object' (correct)
P4 sql.json(obj) via unsafe → jsonb_typeof='object' (correct)
The four call sites are changed to pass the raw value. postgres.js v3
auto-encodes objects/arrays for jsonb columns (already what queue.ts:233
relies on for `minion_jobs.data`, never broken). PGLite handles raw values
identically (verified — all three patterns produce object jsonb on PGLite).
Existing data on the user's brain at the time of fix:
subagent_messages.content_blocks: 40/40 corrupt
subagent_tool_executions.input: 39/39 corrupt
subagent_tool_executions.output: 39/39 corrupt
All 118 rows repaired by `gbrain repair-jsonb` after extending TARGETS.
Symptom this resolves: dream synthesize's orchestrator
(`collectChildPutPageSlugs` in src/core/cycle/synthesize.ts:459) queries
`input->>'slug'` to gather slugs the subagent wrote; on string-typed jsonb
that operator returns NULL, so `pages_written` reports 0 even when child
subagents successfully wrote pages. The reverse-write step that mirrors
DB → markdown is then skipped, leaving real synthesized pages stranded
in the DB. After this fix, the orchestrator picks up slugs correctly and
the full dream cycle closes the loop.
Companion changes:
- `repair-jsonb` extended from 5 → 8 columns (subagent_messages.content
_blocks, subagent_tool_executions.{input,output}); guards each target
with `to_regclass()` so pre-v0.15 brains that lack the subagent_*
tables don't throw at upgrade time.
- `doctor`'s `jsonb_integrity` check extended to the same 8 columns
with the same guard. Any future user hitting this bug from upstream
master gets a clear "X rows double-encoded ... Fix: gbrain
repair-jsonb" warning instead of silent dream failures.
- `repair-jsonb.ts` header comment retracted: the prior claim that
parameterized `$N::jsonb` was always safe was wrong; safety came
from queue.ts/etc. passing raw objects, not from the binding form.
Upstream history: PR garrytan#525 was a chmod fix mis-attributed by local commits;
the real outstanding upstream PR is garrytan#539 (zombie lock + post-hoc data
repair via UPDATE) which does NOT patch the write path. Master HEAD still
ships the buggy form. This patch is upstream-worthy as a follow-up to
Tests: 245 pass across minions, subagent-handler, cycle-synthesize,
cycle-patterns, repair-jsonb, migrations-v0_12_2, and doctor. typecheck
clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
package.jsondeclares:bun installsymlinks~/.bun/bin/gbrain→src/cli.ts. Butsrc/cli.tsis checked in with mode 100644, so the symlink target isn't executable. The postinstall hook then runs:…which fails with
Permission deniedand falls through to the "skipped" branch, leaving migrations un-applied on every install.This is exactly issue #388 (papiofficial / OpenClaw, v0.19.0, macOS arm64, Bun 1.3.12). The reported workaround there is
chmod +x ~/.bun/bin/gbrainafter each install — this PR removes the need for it.The fix
One-liner:
git update-index --chmod=+x src/cli.ts. Mode flips from 100644 → 100755. No source change. The file already has a#!/usr/bin/env bunshebang, so it has been intended to be directly executable all along.Verification
ls -l ~/.bun/bin/gbrain→-rw-r--r--after freshbun install -g-rwxr-xr-x, postinstall'sgbrain apply-migrationsruns cleanlybun run gbrain ...invocations (bun runs the .ts directly regardless)Closes
#388
Need help on this PR? Tag
@codesmithwith what you need.