Skip to content

feat(service): add an opt-in supervisor backend for systemd-less hosts (#479) - #487

Merged
lidge-jun merged 1 commit into
devfrom
codex/479-supervisor-backend
Aug 26, 2026
Merged

feat(service): add an opt-in supervisor backend for systemd-less hosts (#479)#487
lidge-jun merged 1 commit into
devfrom
codex/479-supervisor-backend

Conversation

@parkjs101

Copy link
Copy Markdown
Collaborator

What

jaw service --backend supervisor generates a keep-alive loop for hosts with no service manager (container with PID 1 = tini, no systemd). This is the layer that absorbs the ensure-jaw.sh + 60s while-loop the #479 reporter wrote by hand.

Two load-bearing details, both found by running the script rather than reading it

1. Liveness must come from jaw service status, not pgrep.
service status verifies the pidfile against the OS process start time, so a recycled PID reads as dead. A name-matching check cannot see PID recycling and would sit forever beside a dead server — the same false-healthy reading #479 is about. The supervisor branch's status therefore exits 1 when the server is down; the systemd branch's status exits 0 either way, which a loop must not trust.

2. setsid is util-linux — absent on macOS and minimal images.
Running the loop showed every start failing with setsid: command not found, i.e. the supervisor started nothing at all. It is now probed once, with nohup alone as the fallback.

Behavioral verification

Ran the generated loop against a recorder stub:

Scenario Observed
server alive 3 status queries, 0 starts
server dead a start every cycle, 0 errors logged
dead → recovers starts stop once liveness returns
SIGTERM exits within one interval, logs why

Plus sh -n syntax validation (POSIX sh, not bash — minimal images ship dash).

Scope note

detectBackend() does not select this automatically, and a test enforces that. Writing a script and telling the operator where to wire it in is a different act from registering autostart; implying otherwise would repeat the failure #479 reported.

11 unit tests (tests/unit/supervisor-service.test.ts), tsc --noEmit clean.

Refs #479

Stack (merge bottom-up):

# PR Layer Review focus
4 #488 docs — remote/headless guide docs only
3 #487 supervisor backend ← you are here generated sh + liveness contract
2 #486 service guidance operator message correctness
1 #485 doctor detection detection logic + probe seam

Review this PR's diff only; its base is the layer below.

@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

A container whose PID 1 is tini has nothing to register autostart with, so
`jaw service` could only hand back a manual recipe and every operator wrote
the same loop by hand — the #479 reporter shipped an ensure-jaw.sh plus a 60s
while-loop. `jaw service --backend supervisor` now generates that loop.

Two details are load-bearing, and both came out of running the generated
script rather than reading it:

- Liveness is asked of `jaw service status`, which verifies the pidfile
  against the OS process start time. A pgrep-style check cannot see PID
  recycling, and the supervisor would then sit forever beside a dead server —
  the same false-healthy reading #479 is about. The supervisor branch's status
  therefore exits 1 when the server is down; systemd's status branch exits 0
  either way, which a loop must not trust.
- `setsid` is util-linux: absent on macOS and on minimal images. Running the
  loop showed every start failing with "command not found", so the supervisor
  started nothing at all. It is now probed once and `nohup` alone is used when
  it is missing.

Verified by running the loop against a recorder stub: alive → 3 status
queries and 0 starts; dead → a start every cycle with no errors logged;
recovering → starts stop once liveness returns; SIGTERM → exits within one
interval and logs why.

detectBackend does not select this automatically. Writing a script and telling
the operator where to wire it in is a different act from registering autostart,
and implying otherwise would repeat the failure #479 reported.

Refs #479
@parkjs101
parkjs101 force-pushed the codex/479-supervisor-backend branch from 5208725 to a1c97e8 Compare August 26, 2026 10:53
@parkjs101
parkjs101 force-pushed the codex/479-service-guidance branch from 0172cf6 to 419132d Compare August 26, 2026 10:53
Base automatically changed from codex/479-service-guidance to dev August 26, 2026 15:05
@lidge-jun
lidge-jun merged commit 86106c5 into dev Aug 26, 2026
5 checks passed
@lidge-jun
lidge-jun deleted the codex/479-supervisor-backend branch August 26, 2026 15: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.

3 participants