Skip to content

feat(dream): add --break-lock for gbrain-cycle (closes #1591)#2173

Open
lost9999 wants to merge 1 commit into
garrytan:masterfrom
lost9999:feat/dream-break-lock
Open

feat(dream): add --break-lock for gbrain-cycle (closes #1591)#2173
lost9999 wants to merge 1 commit into
garrytan:masterfrom
lost9999:feat/dream-break-lock

Conversation

@lost9999

Copy link
Copy Markdown

Summary

The cycle lock (held by gbrain dream / autopilot) had no CLI escape hatch, so a wedged cycle stranded it indefinitely and gbrain doctor's hint routed operators to gbrain sync --break-lock which couldn't actually break it.

This PR adds gbrain dream --break-lock with the same safety semantics as the existing gbrain sync --break-lock:

  • safe path (no flag): refuses if holder alive, refuses cross-host (process.kill(pid, 0) is invalid across hosts)
  • --max-age <seconds>: atomic TOCTOU-safe age-gated DELETE keyed on (id, holder_pid, last_refreshed_at < NOW() - N * INTERVAL)
  • --force-break-lock: skip guards, atomic DELETE, loud warning that the holder may still be writing
  • --source <id> / --json: per-source scoping (resolves to gbrain-cycle:<id> lock key) and machine-readable output

Doctor at src/commands/doctor.ts:2417+ already routes cycle locks to gbrain dream --break-lock (the if-else chain is in place), so the moment this lands, the existing hint becomes actionable for the first time.

Test plan

  • gbrain dream --break-lock (no cycle lock held) → Lock gbrain-cycle is not held (nothing to break). exit 0
  • gbrain dream --break-lock while a healthy cycle holds it → holder is alive, use --max-age or --force-break-lock exit 1
  • gbrain dream --break-lock --max-age 60 while a long-dead cycle holds it → Broke cycle lock gbrain-cycle ... exit 0
  • gbrain dream --break-lock --force-break-lock → atomic DELETE, warning emitted
  • gbrain doctor after merge → stale_locks WARN should clear

Files

  • src/commands/dream.ts: +129 lines (runCycleLockBreak + formatAgeHuman + --break-lock short-circuit + 3 imports)
  • CHANGELOG.md: +4 lines ([Unreleased] section)

Closes #1591

The cycle lock (held by `gbrain dream`/autopilot) had no CLI escape
hatch, so a wedged cycle stranded it indefinitely and `gbrain doctor`'s
hint routed operators to `gbrain sync --break-lock` which couldn't
actually break it.

Adds `gbrain dream --break-lock` with the same safety semantics as the
existing `gbrain sync --break-lock` (src/commands/sync.ts:810-966):
  - safe path: refuses if holder alive, refuses cross-host
  - --max-age <seconds>: atomic TOCTOU-safe age-gated delete
  - --force-break-lock: skip guards, atomic DELETE, loud warning
  - --source <id>: per-source scoping (resolves to gbrain-cycle:<id>)
  - --json: machine-readable output

Files:
  - src/commands/dream.ts: +129 lines (runCycleLockBreak + formatAgeHuman
    + --break-lock short-circuit + 3 imports)
  - CHANGELOG.md: +4 lines ([Unreleased] section)

Doctor hint follow-up (route cycle locks to this command instead of
`gbrain sync --break-lock`) is intentionally deferred to a separate
commit so this PR stays scoped to the dream-side escape hatch.

Closes garrytan#1591
@lost9999 lost9999 force-pushed the feat/dream-break-lock branch from 49dcac3 to 2da3b82 Compare June 13, 2026 23:31
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.

feat(dream): add --break-lock for gbrain-cycle (parity with sync --break-lock)

1 participant