Start the daemon from whichever hook fires first
A plugin's SessionStart hook never runs, so nothing was starting the daemon: no daemon.log, nothing on :8791, and a manual `glance up` working perfectly. Grok Build dispatches SessionStart from inside session creation (xai-grok-shell, agent_ops.rs -> DispatchSessionStartHook) and resolves it against the session's hook registry as it stands at that moment. That registry comes from discover_hooks(), whose sources are the config layers and the global/project settings files; plugin directories are not among them. Plugin hooks are appended later, under a plugin/ prefix, by reload_hooks_impl and reload_plugins_impl - which run in response to a plugin action, a /hooks reload, or a folder-trust grant. So the entry is always registered after the event it subscribes to has been dispatched. The other thirteen events work because they happen later in the session. There is no boot event to move to, so every recorder boots the daemon instead and whichever fires first wins. The cost is one loopback request to /healthz per event once it is up, which is the steady state. A daemon.lock (O_EXCL, 15s staleness takeover) keeps a burst of concurrent events from starting five daemons and leaving four to die on EADDRINUSE. glance-up.mjs stays wired: it costs nothing when it does not fire, and it is the right hook for the job if that ordering is ever fixed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b586323fdb
commit
966133eeda
@@ -53,8 +53,9 @@ git clone <this repo> ~/.grok/plugins/grok-glance
|
||||
```
|
||||
|
||||
That is the whole thing — no `npm install`, no build. Open `/plugins` in Grok Build and enable
|
||||
**grok-glance**. On the next session start its `SessionStart` hook boots the daemon in the
|
||||
background, and the dashboard is on `http://127.0.0.1:8791`.
|
||||
**grok-glance**. The first hook to fire after that — your next prompt, or the first tool call —
|
||||
starts the daemon in the background, and the dashboard is on `http://127.0.0.1:8791`. (Not the
|
||||
`SessionStart` hook, which for a plugin never runs; see [Hook wiring](#hook-wiring).)
|
||||
|
||||
### …or from a marketplace, by URL
|
||||
|
||||
@@ -244,6 +245,7 @@ Everything lives in `~/.grok/glance` (mode 0700), or `$GLANCE_HOME` if you set i
|
||||
| `events.jsonl` | Append-only event log, one JSON object per line, rotated at 5 MB |
|
||||
| `sessions.json` | The agent roster — label, badge, workspace, state, counts — so a restart comes back with the overview intact. Written debounced, flushed on shutdown; sessions older than 12 hours are dropped on load. |
|
||||
| `daemon.log` | Daemon stdout/stderr |
|
||||
| `daemon.lock` | Held while a hook script is starting the daemon, so a burst of events starts one and not five. Created with `O_EXCL`, deleted on the way out, and ignored by anyone else once 15s stale. |
|
||||
|
||||
Three environment variables override `config.json`, which is mostly useful for testing a second
|
||||
instance without touching your real one:
|
||||
@@ -343,7 +345,8 @@ traverses the tunnel, and it can present the shared secret — hook traffic goes
|
||||
`http://127.0.0.1:8791` and never leaves the machine. So each observed event runs
|
||||
`bin/glance-record.mjs`, which costs a Node start (~40 ms) and POSTs one event. Two entries differ:
|
||||
|
||||
- `SessionStart` runs `bin/glance-up.mjs`, which is what boots the daemon.
|
||||
- `SessionStart` runs `bin/glance-up.mjs`. It is the obvious hook to boot the daemon from, and it
|
||||
never runs — see below.
|
||||
- `PreToolUse` is wired **twice** — a recording entry for the timeline, and a second entry matching
|
||||
only `^(Bash|Write|Edit|MultiEdit|NotebookEdit)$` that runs `bin/glance-approve.mjs`. PreToolUse
|
||||
is the only blocking event, and a command hook is the only documented way to return a deny
|
||||
@@ -354,6 +357,30 @@ traverses the tunnel, and it can present the shared secret — hook traffic goes
|
||||
The hook scripts use nothing but the Node standard library and always exit 0 unless they are
|
||||
deliberately denying — including when the daemon rejects their token.
|
||||
|
||||
### Why every recorder boots the daemon
|
||||
|
||||
A plugin gets no usable boot event, so `bin/glance-record.mjs` starts the daemon itself when it
|
||||
finds it missing, and whichever event fires first wins.
|
||||
|
||||
`SessionStart` looks like the right answer and cannot work. Grok Build dispatches it from inside
|
||||
session creation (`xai-grok-shell`, `agent_ops.rs` → `SessionCommand::DispatchSessionStartHook`),
|
||||
and the dispatch resolves against the session's hook registry **as it stands at that moment**. That
|
||||
registry comes from `discover_hooks()`, whose sources are the config layers and the global/project
|
||||
settings files — `~/.grok/settings.json`, `<git_root>/.grok/hooks`, the vendor-compat paths. Plugin
|
||||
directories are not among them. Plugin hooks are appended separately, under a `plugin/` prefix, by
|
||||
`reload_hooks_impl` and `reload_plugins_impl` — both of which run later, in response to a plugin
|
||||
action, a `/hooks reload`, or a folder-trust grant. So a plugin's `SessionStart` entry is always
|
||||
registered after `SessionStart` has already been dispatched, and is never called. Every other event
|
||||
this plugin subscribes to happens later in the session, once the plugin registry has landed, which
|
||||
is why they all work.
|
||||
|
||||
The symptom, if you hit this from the other end: no `daemon.log` at all, nothing on `:8791`, and a
|
||||
manual `glance up` working perfectly.
|
||||
|
||||
Asking costs one loopback request to `/healthz` per event, which is the steady state once the
|
||||
daemon is up. The spawn path is taken once. A `daemon.lock` (`O_EXCL`, 15s staleness) keeps a burst
|
||||
of concurrent events from starting five daemons and leaving four of them to die on `EADDRINUSE`.
|
||||
|
||||
## Deliberately omitted
|
||||
|
||||
Not oversights — decisions:
|
||||
|
||||
Reference in New Issue
Block a user