Skip to content

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
garrytan:masterfrom
notjbg:fix/cli-executable-bit
Closed

fix(install): mark src/cli.ts executable so bin symlink works after bun install (closes #388)#525
notjbg wants to merge 1 commit into
garrytan:masterfrom
notjbg:fix/cli-executable-bit

Conversation

@notjbg

@notjbg notjbg commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

The bug

package.json declares:

"bin": { "gbrain": "src/cli.ts" }

bun install symlinks ~/.bun/bin/gbrainsrc/cli.ts. But src/cli.ts is checked in with mode 100644, so the symlink target isn't executable. The postinstall hook then runs:

command -v gbrain >/dev/null 2>&1 && gbrain apply-migrations --yes --non-interactive

…which fails with Permission denied and 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/gbrain after 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 bun shebang, so it has been intended to be directly executable all along.

Verification

  • Before: ls -l ~/.bun/bin/gbrain-rw-r--r-- after fresh bun install -g
  • After: -rwxr-xr-x, postinstall's gbrain apply-migrations runs cleanly
  • No behavior change on Windows (mode bits are ignored there)
  • No effect on bun run gbrain ... invocations (bun runs the .ts directly regardless)

Closes

#388


View in Codesmith
Need help on this PR? Tag @codesmith with what you need.

  • Let Codesmith autofix CI failures and bot reviews

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
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.
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.
@garrytan

Copy link
Copy Markdown
Owner

Closing in favor of #307, which was the first submission of this exact fix (chmod +x on src/cli.ts). Same one-line mode change, both correct. Whichever lands will close #388. Thanks for catching the issue independently — that's signal that this is a real footgun worth fixing soon.

@garrytan garrytan closed this May 24, 2026
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.
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.

2 participants