Skip to content

feat: add bridge dev command that runs devCommand under health supervision - #10

Merged
vercel-eddie merged 1 commit into
mainfrom
eddie/exec-wait-healthy
May 20, 2026
Merged

vercel-eddie merged 1 commit into
mainfrom
eddie/exec-wait-healthy

Conversation

@vercel-eddie

@vercel-eddie vercel-eddie commented May 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds bridge dev <bridge> that launches the devCommand from devcontainer.json (e.g. "pnpm dev"), streams its output, and waits until the source deployment's probes report the local app healthy. PID is persisted across invocations so re-running cleanly kills the previous server.

$ bridge dev api-devbox
Started: pnpm dev (pid 12345)
Logs:    /Users/eddie/.bridge/logs/dev-api-devbox.log
Waiting for probes to report healthy...
┌── Running: pnpm dev ──┐
│ ▲ Next.js 14.x        │
│ ✓ Ready in 2.1s       │
└───────────────────────┘
✓ Healthy (pid 12345)

What's in this PR

bridge dev command — positional bridge name, --timeout (default 2m, BRIDGE_DEV_TIMEOUT).

  • Reads devCommand from devcontainer.json (string or array)
  • Starts the command via devcontainer exec with Setsid so it survives the CLI exit
  • Streams the log file into the same interact.NewViewport bridge create uses
  • Re-running kills the previously tracked PID first (SIGTERM with 3s grace → SIGKILL)
  • Exit precedence: dev process exits → all probes pass → --timeout

Probe RPC on InterceptorService — synchronous, stateless "is the app responding right now?" check. Bypasses the threshold-tracked GetStatus view so a stale HEALTHY from a previous session can't fool the new run. Bridge dev polls Probe; bridge get keeps using GetStatus for the smoothed steady-state view.

bridge get APPLICATION column — new application_status field on GetCommandResponseBridge. Queried in parallel per bridge with a 1.5s timeout. Values: healthy / unhealthy / starting / unreachable / stopped. Defaults to healthy when the source has no probes.

--start-container / -s on bridge create — starts the devcontainer after creating the bridge but doesn't attach. --connect now implies --start-container. Useful for agent flows that want a container running without holding the terminal.

Install path moved to ~/.local/bin/bridge — no sudo, no xattr, no Gatekeeper workaround needed (curl doesn't quarantine its downloads). Override with BRIDGE_INSTALL_DIR=.... Hint about PATH is printed only when the directory isn't already on it.

linuxBinaryPath no longer dev-only-relative — prefers ~/.bridge/bin/bridge-linux when present so bridge create works from any CWD; falls back to ./dist/bridge-linux only for the in-repo development workflow.

Misc:

  • Session proto extended with dev_pid / dev_started_at / dev_command
  • session.Write(*Session) so callers can Load → mutate → Write without losing fields; Save still resets dev state on fresh creates
  • PID is persisted regardless of dev-command outcome — agents/next runs can use it for diagnostics or cleanup even on a failed start

Tests

  • pkg/probe/*_test.go — Probe interface contract, HTTP/TCP/Exec handlers
  • pkg/devcontainer/devcommand_test.go — string / array / missing / invalid devCommand
  • pkg/session/session_test.go — fresh save, full round-trip with dev fields, Save resets, idempotent Delete
  • pkg/commands/dev_test.go — orchestrator state machine via fakeInterceptor (Probe shape now): immediate healthy, exit-wins-over-health, timeout, half-passing, no-probes-defaults-to-healthy, mid-wait healthy transition, context cancel, mid-wait exit
  • Full unit suite + -race clean

🤖 Generated with Claude Code

@vercel-eddie
vercel-eddie force-pushed the eddie/exec-wait-healthy branch 5 times, most recently from aa67689 to 7a125f9 Compare May 20, 2026 22:49
…rvision

Adds a new top-level command that launches the `devCommand` field declared
in devcontainer.json (a string or array of strings — e.g. "pnpm dev") and
waits for the source deployment's liveness/readiness/startup probes to
report the local app as healthy.

The command exits when:
  1. The dev command process exits (returns that exit code)
  2. All configured probes report healthy (success; pid is in the response)
  3. --timeout elapses (default 2m, BRIDGE_DEV_TIMEOUT-configurable)

Re-running `bridge dev` for the same bridge first kills any previously
tracked dev process so the new server replaces the old one cleanly. The
PID is persisted in ~/.bridge/sessions/<bridge>.json so we can find it
across invocations even if the bridge CLI was interrupted in between.

Implementation:
- proto/session.proto: track dev_pid / dev_started_at / dev_command on Session
- proto/command.proto: add DevCommandResponse + DevCommandReason enum
- pkg/devcontainer: Config.DevCommand() reader for the new field;
  StartDetached / KillProcess / IsProcessAlive / ReadDetachedExitCode
  primitives on CLIClient. The detached command runs via a `setsid` wrapper
  through `devcontainer exec`, so the dev process survives our disconnect
  but still gets the devcontainer's remoteUser, containerEnv, and
  feature-injected env applied.
- pkg/session: add Write(s *Session) so callers can preserve fields across
  updates. Save still resets dev state — fresh bridges shouldn't inherit
  stale PIDs.
- pkg/commands/dev.go: ties it together. waitForDevHealthy races the three
  signals in the documented precedence order; exit-before-healthy returns
  the underlying exit code instead of swallowing it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel-eddie
vercel-eddie force-pushed the eddie/exec-wait-healthy branch from 7a125f9 to 7ddd9f8 Compare May 20, 2026 22:52
@vercel-eddie
vercel-eddie merged commit fa1bdd0 into main May 20, 2026
5 checks passed
@vercel-eddie
vercel-eddie deleted the eddie/exec-wait-healthy branch May 20, 2026 23:05
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