Files
iceBear67andClaude Opus 5 966133eeda 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>
2026-08-09 08:04:03 +00:00

53 lines
1.8 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* SessionStart hook: make sure the glance daemon is running, then record the event.
*
* Note that as of Grok Build's current hook wiring this never actually runs: a plugin's
* `SessionStart` entry is registered after `SessionStart` has already been dispatched, so the
* event finds no plugin hooks to call. `ensureDaemon` in glance-lib.mjs has the details, and
* bin/glance-record.mjs is what really boots the daemon.
*
* It stays wired anyway. It costs nothing when it does not fire, it is the correct hook for the
* job on the day that ordering is fixed, and it keeps the answer to "what starts this thing"
* in the obvious place.
*
* It always exits 0 — a monitoring dashboard must never be the reason a session fails to start.
*/
import {
baseUrl,
ensureDaemon,
envEnvelope,
hookHeaders,
postJson,
readConfig,
readStdinJson,
} from "./glance-lib.mjs";
const payload = envEnvelope(await readStdinJson());
const cfg = readConfig();
try {
// hooks/hooks.json allows this hook 20s; leave most of it as headroom.
const up = await ensureDaemon(cfg, {
waitMs: 8000,
startedBy: "session-start-hook",
onMissingBuild: (entry) => {
// dist/ ships with the plugin, so this means an incomplete checkout. Say so once, on
// stderr, where it is recorded but harmless — a hook must never fail a session.
process.stderr.write(
`[grok-glance] ${entry} is missing - run \`npm install && npm run build\` in the plugin root\n`,
);
},
});
if (up) {
// hookHeaders() is read here, not at import time: on a first-ever run the daemon we just
// spawned is what created hook.secret.
await postJson(`${baseUrl(cfg)}/hook/record`, payload, 2500, hookHeaders());
}
} catch {
// Fail open, always.
}
process.exit(0);