Skip to content

Bring the gateway up at the end of Inkbox setup - #77

Merged
dimavrem22 merged 4 commits into
mainfrom
feat/setup-gateway-restart
Jul 28, 2026
Merged

dimavrem22 merged 4 commits into
mainfrom
feat/setup-gateway-restart

Conversation

@dimavrem22

@dimavrem22 dimavrem22 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

hermes inkbox setup wrote credentials, channels, realtime config and a signing key to ~/.hermes/.env, then printed hermes gateway run and exited. None of it reached the agent until someone re-read that line and acted on it — and on a rerun, where a gateway is already up on the old config, run was the wrong command entirely.

Setup now ends with a real step: detect the gateway, then offer to act on it. Every prompt defaults to yes.

Detected Prompt Runs
Gateway running Restart the gateway now? hermes gateway restart
Not running, service slot exists Launch the gateway now? hermes gateway start
Not running, no service slot Install and launch the gateway service now? hermes gateway install
Can't tell — prints both commands, asks nothing

and closes on a banner rather than a to-do list with nothing left to do:

╭────────────────────────────────────────────────────╮
│ Your Hermes agent is set up and running on Inkbox. │
│                                                    │
│   Inkbox identity:   dimas-clanker                 │
│   Check its health:  hermes inkbox doctor          │
╰────────────────────────────────────────────────────╯

The next-steps list survives only for the case it was written for: nothing listening, so hermes gateway run really is the next thing.

Detection

_gateway_runtime_state() returns (running, service_installed):

  1. hermes_cli.gateway.get_gateway_runtime_snapshot() — the CLI's unified view, covering systemd/launchd/s6 service slots and a bare hermes gateway run process.
  2. Falls back to gateway.status.is_gateway_running() (the PID/lock file) when that import fails.
  3. (None, False) when neither is importable — the wizard prints both commands rather than guessing.

Two fixes that came out of running this on macOS

Exit codes are not evidence. hermes gateway install hit a launchd bootstrap failure, fell back to starting a plain background gateway, and exited 0:

⚠ launchd cannot manage the gateway on this macOS version (launchctl bootstrap exit 5).
✓ Started gateway as a background process instead
✓   Gateway service installed.
    Start it with: hermes gateway start

Both of those last lines were wrong — launchd installed nothing, and a second start on top of the running process would have duplicated the gateway. get_running_pid() only reports a gateway once it has taken its runtime lock and written its PID record, so a probe fired the instant install returns races the process it just spawned. Now _wait_for_gateway_running() polls for up to 15s, and an unconfirmed start or install reports Could not confirm the gateway came up and points at hermes gateway status rather than handing out a start command.

A stale sibling can own Inkbox. hermes gateway stop no-ops whenever the launchd plist exists but the job was never bootstrapped — it takes the launchd path on get_launchd_plist_path().exists(), boots out a job that was never loaded, and never reaches the code that kills the detached process:

Boot-out failed: 3: No such process
✓ Service stopped
✓ Stopped hermes-gateway service

Reported success, killed nothing. Every later Launch the gateway now? stacked another one on top. Only one can hold the Inkbox platform lock and the adapter's listen port (adapter.py:1788, and the Port %d already in use check below it), so the extras come up with Inkbox dead — and the survivor may predate the config just written. Inbound messages then have no receiver while this step reports success.

The runtime snapshot already carries a deduplicated gateway_pids tuple and this step was collapsing it to a bool. It now reports when more than one is alive, points at hermes gateway stop --all, and suppresses the success line — in the restart, start and install branches.

Both of those are host bugs and not fixable from the plugin; this only stops the wizard reporting a success that is not real.

Decisions worth a look

  • Foreground run is never executed, only printed — it would take over the wizard's terminal. That is why the no-slot branch offers install (which registers a supervised slot and asks its own start-now question) rather than run.
  • install gets no subprocess timeout — it prompts on this same terminal, so a clock would kill it mid-answer. restart/start are bounded at 180s; the CLI's own service-restart timeout is 90s and a restart drains in-flight agent runs first.
  • Self-restart guard: skipped when _HERMES_GATEWAY=1. Hermes refuses that itself to avoid a KeepAlive kill loop.
  • Profile-safe: the subprocess inherits HERMES_HOME, which the CLI exports before argparse, so hermes -p foo inkbox setup targets foo's gateway.

Also

  • Removed two now-contradictory hints: Start the gateway with: hermes gateway run in the closing agent summary, and the run/restart commands printed mid-flow in the iMessage walkthrough. Both defer to the new step.
  • Version 0.2.5 → 0.2.6.

Verified live on macOS across the restart and launch branches, including a launchd fallback to a background process. 22 tests cover every branch, the decline paths, the poll behaviour and its timeout, duplicate detection, the banner's content and box geometry, and the hermes-not-on-PATH fallback (python -m hermes_cli.main). 290 pass; 3 pre-existing tests/test_external_reply.py failures come from a missing aiohttp in this environment and are unrelated — confirmed against a clean checkout.

Siblings: inkbox-ai/openclaw-plugin#46, inkbox-ai/claude-code-plugin#47, inkbox-ai/codex-plugin#29, inkbox-ai/opencode-plugin#11.

dimavrem22 and others added 2 commits July 28, 2026 02:52
Nothing the setup wizard writes reaches the agent until the gateway
reloads .env, and the wizard only ever printed `hermes gateway run` as a
parting instruction. Finish setup by detecting the gateway and offering
to act on it.

Detection reads the CLI's runtime snapshot (systemd/launchd/s6 service
slots plus a bare `gateway run` process), falling back to the gateway's
own PID file, and returns liveness plus whether a service slot exists.
Three branches follow, each defaulting to yes: restart a running
gateway, start an installed-but-down one, or install and launch when
there is no service slot at all. Foreground `run` is only ever printed —
executing it would take over the wizard's terminal.

The restart is skipped when setup is running inside the gateway process,
which Hermes refuses anyway to avoid a KeepAlive kill loop, and the
profile carries into the subprocess through the inherited HERMES_HOME.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Found running this on macOS: launchd refused the bootstrap (exit 5),
`hermes gateway install` fell back to "Started gateway as a background
process instead" and exited 0, and the immediate re-check still reported
nothing running. The step then printed "Gateway service installed. Start
it with: hermes gateway start" -- wrong twice over. launchd had not
installed anything, and a second start on top of the background process
would have duplicated the gateway. The closing next-steps list repeated
the mistake with `hermes gateway run`.

get_running_pid() only reports a gateway once it has taken its runtime
lock and written its PID record, which happens partway through startup,
so a probe fired the instant install returns races it. Poll for up to 15s
instead. When the window still closes unconfirmed, say so and point at
`hermes gateway status` rather than handing out a start command, since
install reported success and may well have brought one up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dimavrem22

Copy link
Copy Markdown
Contributor Author

Pushed 228e7d1 for a real bug from local macOS testing.

hermes gateway install hit a launchd bootstrap failure, fell back to starting a plain background gateway, and exited 0:

⚠ launchd cannot manage the gateway on this macOS version (launchctl bootstrap exit 5).
✓ Started gateway as a background process instead
✓   Gateway service installed.
    Start it with: hermes gateway start

Both of those last two lines were wrong. launchd installed nothing, and a second start on top of the running background process would have duplicated the gateway. Next steps then repeated it with hermes gateway run.

Cause: get_running_pid() only reports a gateway once it has taken its runtime lock and written its PID record, which happens partway through startup — so a probe fired the instant install returns races the process it just spawned.

Fix is two-part:

  • _wait_for_gateway_running() polls for up to 15s instead of probing once.
  • An unconfirmed install no longer answers a successful install with "now go start one". It reports Gateway install completed. and points at hermes gateway status, and returns True so the closing list drops hermes gateway run too.

4 tests updated/added, including one asserting the unconfirmed path emits neither gateway start nor gateway run. The same flaw and fix went into openclaw-plugin#46.

Found debugging why a freshly configured agent never answered an SMS.
`hermes gateway stop` no-ops on macOS whenever the launchd plist exists
but the job was never bootstrapped -- it boots out nothing, reports
success, and leaves the detached process alive. Every subsequent run of
this step then started another one on top.

Only one gateway can hold the Inkbox platform lock and the adapter's
listen port, so the extras come up with Inkbox dead, and the survivor
may predate the config just written. This step still printed "Gateway
started with the new Inkbox config" over all of it.

Count the gateway PIDs the runtime snapshot already carries. Above one,
say so and point at `hermes gateway stop --all` instead of claiming the
new config is live. The start branch also confirms liveness before
claiming it, the way install already did.

The stop no-op and the launchd bootstrap failure are host bugs and are
not fixable from here; this only stops the wizard from reporting a
success that is not real.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dimavrem22

Copy link
Copy Markdown
Contributor Author

Pushed 08fad79 — third fix from local testing, this one found debugging why a freshly configured agent never answered an SMS.

What happened: hermes gateway stop no-ops on macOS whenever the launchd plist exists but the job was never bootstrapped. gateway.py takes the launchd path on get_launchd_plist_path().exists(), boots out a job that was never loaded, sets service_available = True, and so never reaches the code that kills the detached process:

Boot-out failed: 3: No such process
✓ Service stopped
✓ Stopped hermes-gateway service

Reported success, killed nothing. Every later Launch the gateway now? stacked another one on top — three alive by the end.

Only one can hold the Inkbox platform lock and the adapter's listen port (adapter.py:1788, then the Port %d already in use check right below), so the extras come up with Inkbox dead, and the survivor predated the phone number the wizard had just provisioned. Inbound SMS had no receiver. Meanwhile this step printed Gateway started with the new Inkbox config. over the whole thing.

Fix: the runtime snapshot already carries gateway_pids, and I was collapsing it to a bool. Now:

  • _warn_if_multiple_gateways() reports when more than one is alive and points at hermes gateway stop --all, and the success line is suppressed when it fires — in the restart, start, and install branches.
  • The start branch confirms liveness via _wait_for_gateway_running() before claiming success, matching what install already did.

The stop no-op and the launchd bootstrap failure are Hermes host bugs, not fixable from the plugin. This only stops the wizard from reporting a success that isn't real.

4 new tests. 290 pass.

The wizard ended on a "Next steps" list even after it had just started or
restarted the gateway, which reads as unfinished work when there is none
left to do.

When the gateway is live, close on a banner naming the identity the agent
now runs as and the one command worth knowing:

  ╭────────────────────────────────────────────────────╮
  │ Your Hermes agent is set up and running on Inkbox. │
  │                                                    │
  │   Inkbox identity:   dimas-clanker                 │
  │   Check its health:  hermes inkbox doctor          │
  ╰────────────────────────────────────────────────────╯

The next-steps list stays for the case it was written for: nothing is
listening, so `hermes gateway run` is genuinely the next thing to do.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dimavrem22
dimavrem22 marked this pull request as ready for review July 28, 2026 05:13
@dimavrem22
dimavrem22 merged commit ba5ac4f into main Jul 28, 2026
21 checks passed
@dimavrem22
dimavrem22 deleted the feat/setup-gateway-restart branch July 28, 2026 06:32
dimavrem22 added a commit that referenced this pull request Jul 28, 2026
0.2.6 shipped with the gateway-startup work in #77, so the fleet-shared
number moves on for this feature.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dimavrem22 added a commit that referenced this pull request Jul 29, 2026
0.2.6 shipped with the gateway-startup work in #77, so the fleet-shared
number moves on for this feature.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dimavrem22 added a commit that referenced this pull request Jul 29, 2026
* Add dedicated-outbound iMessage group chats

* Bump to 0.2.7 for the group-chat release

0.2.6 shipped with the gateway-startup work in #77, so the fleet-shared
number moves on for this feature.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (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