Skip to content

v1.39.1.0 fix(release-daemon): homebrew-aware PATH + doctor subcommand + upgrade migration#37

Merged
anbangr merged 4 commits into
mainfrom
feat/release-daemon-path-fix-and-doctor
May 16, 2026
Merged

v1.39.1.0 fix(release-daemon): homebrew-aware PATH + doctor subcommand + upgrade migration#37
anbangr merged 4 commits into
mainfrom
feat/release-daemon-path-fix-and-doctor

Conversation

@anbangr

@anbangr anbangr commented May 16, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes a real-world bug on this dev box: 61 PRs queued in ~/.gstack/build-state/release-queue with nothing draining them. Root cause: launchd's default PATH=/usr/bin:/bin:/usr/sbin:/sbin omits /opt/homebrew/bin, so the daemon's gh pr list calls returned ENOENT and every PR was marked blocked: gh pr view failed. The daemon was running, but functionally inert.

Substantive changes:

  • Hybrid PATH in plist/systemdreleaseDaemonDefaultPath(env) merges known-good prefixes (/opt/homebrew/bin, /usr/local/bin, ~/.local/bin) with the install-time process.env.PATH, deduplicated, ending in system defaults. The launchd plist now emits <EnvironmentVariables> with PATH+HOME; the systemd user unit now emits Environment="PATH=...".
  • New gstack-build release-daemon doctor subcommand — reports queue depth (by status), plist/unit presence + EnvironmentVariables check, launchctl/systemctl load state, the loaded PATH, tool resolvability under that PATH, last 16KB of err.log tail, and a single-word verdict. Exit code 0 only when HEALTHY.
  • gstack-upgrade migration v1.39.1.0 — three independent one-time banner variants gated by per-state touchfiles: Notice A (queue + no daemon installed), macOS combined Notice B (plist exists but needs reload — either not loaded, or stale without EnvironmentVariables), Linux Notice B (unit exists but lacks Environment="PATH=). Idempotent; banner doesn't repeat once the touchfile is set.
  • installReleaseDaemon stdout now teaches both the first-install launchctl load command and the re-install launchctl unload && launchctl load pair (and the daemon-reload && restart equivalent on Linux).

Commits:

  • 536817b5 feat(release-daemon): doctor subcommand + homebrew-aware PATH for launchd/systemd
  • a0ce8797 chore: bump package.json version to 1.39.1.0 to match VERSION
  • 0278e756 fix(test): add chooseMergePath to cli.test.ts imports (formatter-applied, fixes pre-existing test)
  • 8f4e81e6 fix: cap release-daemon doctor err.log read at 16KB (adversarial finding from /ship)

Test Coverage

COVERAGE: 21/24 code paths tested (87.5%) | User flows: 9/9 (100%)
QUALITY: ★★★:14 ★★:5 ★:2 | GAPS: 3 (doctor verdict variants, manually verified live)
  • 9 new tests in build/orchestrator/__tests__/cli.test.ts (4 PATH helper tests, 4 doctor scenarios, 1 doctor parse smoke)
  • 8 new scenarios in build/orchestrator/__tests__/migration-v1.39.1.0.test.ts (6 darwin + 2 linux, mocks launchctl list via fake-binary on PATH)
  • Live verification on the broken machine: gstack-build release-daemon doctor correctly reports Verdict: DAEMON_NOT_LOADED with 58 queued records and tool resolvability of gh/git/bun.

Tests: 192 → 211 (+19 new). 0 new regressions.

Pre-Landing Review

No critical issues. Adversarial pass found one informational finding — unbounded readFileSync on err.log — and auto-fixed it (16KB cap, commit 8f4e81e6).

Plan Completion

10/10 plan tasks done. Plan file: ~/.claude/plans/the-release-daemon-is-distributed-orbit.md.

  • T1 releaseDaemonDefaultPath helper ✓
  • T2 renderLaunchdReleaseDaemonPlist patched ✓
  • T3 renderSystemdReleaseDaemonService patched ✓
  • T4 installReleaseDaemon stdout updated ✓
  • T5 doctor subcommand added ✓
  • T6 cli.test.ts extended ✓
  • T7 migration v1.39.1.0.sh written ✓
  • T8 migration test suite written ✓
  • T9 VERSION + CHANGELOG ✓
  • T10 TODOS entry ✓

TODOS

  • Added P3: Migration telemetry — a gstack-migration-log binary that migration scripts can call to emit banner-fired and state-resolved events.
  • Added P0: 9 pre-existing test failures observed in /ship triage on this branch — all unrelated (5 global-discover timeouts, 2 brain-rename stale-refs, 1 fork-overlay test, 1 dev-profile test). Verified pre-existing by stashing diff and reproducing on main.

Documentation

Skipped automated /document-release for this PR — the new doctor subcommand is documented inline in gstack-build help text and in the CHANGELOG release summary. No other doc files needed updates for a single new subcommand + bug fix.

Test plan

  • bun test build/orchestrator/tests/cli.test.ts — 202 pass / 0 fail
  • bun test build/orchestrator/tests/migration-v1.39.1.0.test.ts — 6 pass / 2 skip (linux) / 0 fail
  • bash -n gstack-upgrade/migrations/v1.39.1.0.sh — syntax OK
  • Migration smoke test in temp HOME — Notice A fires, touchfile created, second run silent
  • Live: gstack-build release-daemon doctor on broken-state machine — reports DAEMON_NOT_LOADED with full diagnostic

🤖 Generated with Claude Code

anbangr and others added 4 commits May 16, 2026 07:35
…nchd/systemd

Release daemon's plist used launchd's default PATH (omits /opt/homebrew/bin
on Apple Silicon), so spawned `gh`/`bun`/`git` hit ENOENT and the queue
froze with every PR marked `blocked: gh pr view failed`. Plist + systemd
unit now bake a homebrew-aware PATH; a new `gstack-build release-daemon
doctor` reports queue depth, daemon load state, PATH sanity, tool resolv-
ability, and last 5 log lines. A v1.39.1.0 upgrade migration prints a
one-time banner with the exact re-install + reload commands when the
queue has records and either no daemon is installed or the plist
predates the PATH fix. Two independent touchfiles keep the install-
needed and reload-needed notices separable.

VERSION → 1.39.1.0. cli.ts +355, cli.test.ts +208 (9 new tests covering
the PATH helper and doctor report), migration script + test suite
(6 darwin + 2 linux scenarios with mocked launchctl). One follow-up
captured in TODOS.md (P3 migration telemetry).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
gen-skill-docs test asserts package.json.version === VERSION.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The v1.39.1.0 commit added a test asserting chooseMergePath(null) ===
'ship-and-deploy' but didn't import the function. cli.test.ts now passes
202/202 (was 201/202 with ReferenceError on the new case).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adversarial pass during /ship surfaced unbounded readFileSync on
~/.gstack/release-daemon.err.log. On a long-running daemon the log
can grow without bound (no log rotation directive in the plist).
Cap the read at the last 16KB before splitting — we only ever
emit the last 5 non-empty lines, so 16KB is plenty.

Also captures a P0 TODO for 9 pre-existing test failures observed
during this branch's /ship triage. None are caused by this branch
(verified by stashing the diff and reproducing on main); they
should be triaged in a follow-up to restore a green baseline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@anbangr anbangr merged commit 548baca into main May 16, 2026
@anbangr anbangr deleted the feat/release-daemon-path-fix-and-doctor branch May 16, 2026 00:05
anbangr added a commit that referenced this pull request May 16, 2026
….0 (release-daemon)

Both my fork and upstream chose v1.39.1.0 in parallel:
  - Fork (this PR #37 at 548baca): release-daemon PATH fix + doctor subcommand + migration
  - Upstream (PR garrytan#1512 at f589770): EXIT PLAN MODE GATE blocking checklist

Per user choice (B), kept my v1.39.1.0 entry as the fork's headline release
and folded upstream's release notes in as a sub-section "Upstream sync:
ExitPlanMode gate" inside the same v1.39.1.0 entry. The runtime at
v1.39.1.0 now contains both feature sets. A disclosure note at the top of
the entry calls out the parallel-version situation explicitly. Future fork
ships will bump past v1.39.1.0 cleanly.

Conflicts resolved:
  - CHANGELOG.md: kept my v1.39.1.0 heading + body; added upstream content
    as an "### Upstream sync" sub-section within the same entry.
  - scripts/resolvers/index.ts: kept fork-local `generateBuildCliCandidates`,
    added upstream's `generateExitPlanModeGate` import + resolver entry.
  - test/gen-skill-docs.test.ts: took upstream's preamble assertion rewrite
    (new behavior: no `NO REVIEWS YET` in operational skills, `EXIT PLAN
    MODE GATE` reference instead) — verified the regenerated office-hours
    SKILL.md matches the new test.

Side effects of upstream's preamble retoning: 53 generated SKILL.md files
regenerated by `bun run gen:skill-docs --host all`. Diff is mechanical:
"Plan Status Footer" section in every skill's preamble now reads as a
neutral forward reference to EXIT PLAN MODE GATE instead of imposing
review-report rules on operational skills.

Test status:
  - `bun test`: 1 failure, identical to the pre-existing P0 TODO from PR
    #37 (Step 4.8 fork overlay test). 8 other pre-existing failures
    apparently fixed by upstream changes (down from 9 to 1).
  - cli.test.ts (release-daemon work): all 202 tests pass.
  - migration-v1.39.1.0.test.ts: all 8 scenarios pass (6 darwin + 2 linux
    skipped on darwin host).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant