Skip to content

feat: zero-config hook relay via --settings inline curl - #55

Merged
aterrylu merged 4 commits into
mainfrom
terry/auto-install-hooks
Mar 24, 2026
Merged

feat: zero-config hook relay via --settings inline curl#55
aterrylu merged 4 commits into
mainfrom
terry/auto-install-hooks

Conversation

@aterrylu

@aterrylu aterrylu commented Mar 24, 2026

Copy link
Copy Markdown
Owner

Summary

Replace the file-based hook relay with per-session hook injection via --settings flag. Zero config, zero files, zero user action.

How it works

createSession() → args.push("--settings", JSON.stringify({ hooks: {...} }))
                       ↓
Claude Code starts with hooks pre-configured
                       ↓
Each hook event fires: curl -d @- $AUTONOMOS_SERVER/api/hooks/$SESSION_ID
                       ↓
Server receives event → derives agent status → dashboard shows icon
  • AUTONOMOS_SERVER and AUTONOMOS_SESSION_ID already injected by buildEnv()
  • Hooks merge with user's existing hooks (empirically verified)
  • Sessions outside autonomOS are completely unaffected

What this eliminates

Before After
autonomos-relay.sh script file No files
installHooks.ts modifying settings.json Deleted
User needs to click "Install" or run setup Nothing needed
Global ~/.claude/settings.json mutation Zero config touch

Test plan

  • Start a session via dashboard → hook events arrive at /api/hooks
  • Status icons update in real-time on sidebar
  • User's existing hooks still fire (merge, not replace)
  • Sessions started outside dashboard have no autonomOS hooks
  • curl failure is silent (2s timeout, async, >/dev/null)

🤖 Generated with Claude Code

Server now ensures the autonomos-relay.sh hook script is installed
and registered in Claude Code's settings.json on every startup.

- installHookRelay() runs at startup, after claude binary validation
- Copies hook script from packages/server/hooks/ to ~/.claude/hooks/
- Registers on 10 Claude Code hook events with async: true
- Idempotent: skips if already installed and up to date
- Updates script if bundled version is newer
- Uses ~ paths in settings.json (portable across machines)
- Non-fatal: warns on failure, server continues

No manual setup needed — just start the server and hooks are ready.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread packages/server/src/installHooks.ts Outdated
hooks: [
{
type: "command",
command: `~/.claude/hooks/${HOOK_FILENAME}`,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Warning

Problem: ~ in the hook command path may not expand in all execution environments.

Why it matters: Claude Code likely runs hook commands via execFile or similar (no shell), which means ~ is treated as a literal character — the hook silently never runs. Since async: true suppresses errors, this failure is invisible. The whole feature would be broken in those environments.

Suggested fix:

// Use the already-resolved HOME constant instead of ~
command: `${HOME}/.claude/hooks/${HOOK_FILENAME}`,

This is portable and guaranteed to expand correctly at startup time.

Comment thread packages/server/src/installHooks.ts Outdated

if (changed) {
settings.hooks = hooks;
writeFileSync(SETTINGS_FILE, `${JSON.stringify(settings, null, 2)}\n`);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Warning

Problem: writeFileSync on settings.json is not atomic — a crash mid-write corrupts the file.

Why it matters: If the server process is killed while writing, Claude Code ends up with a partial/invalid settings.json, which likely breaks every Claude Code session on the machine until manually repaired.

Suggested fix:

import { writeFileSync, renameSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

// Write to a temp file, then atomically rename into place
const tmp = join(tmpdir(), `settings-${process.pid}.json`);
writeFileSync(tmp, `${JSON.stringify(settings, null, 2)}\n`);
renameSync(tmp, SETTINGS_FILE);

rename is atomic on POSIX — either the old or new file is visible, never a partial write.

@nox-0x nox-0x left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, well-structured feature — idempotent design is solid and the graceful error handling is the right call. Two warnings worth fixing before relying on this in prod: (1) the ~ in the hook command path risks silent failure if Claude Code uses execFile rather than a shell; swap for the HOME constant already in scope. (2) the settings.json write should go through a temp-file + rename to avoid corrupting Claude Code config on a hard crash.

Replace the file-based hook relay (autonomos-relay.sh + installHooks.ts)
with per-session hook injection via Claude Code's --settings flag.

How it works:
- createSession() passes --settings with hooks config as inline JSON
- Each hook event fires: curl -d @- $AUTONOMOS_SERVER/api/hooks/$SESSION_ID
- AUTONOMOS_SERVER and AUTONOMOS_SESSION_ID already injected by buildEnv()
- Merges with user's existing hooks (empirically verified)

What this eliminates:
- No relay script file to install
- No global settings.json mutation
- No install button needed
- No user action required
- Sessions outside autonomOS are completely unaffected

Deleted: installHooks.ts (no longer needed)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aterrylu aterrylu changed the title feat: auto-install hook relay on server startup feat: zero-config hook relay via --settings inline curl Mar 24, 2026
aterrylu and others added 2 commits March 24, 2026 01:12
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
' -d @- "${AUTONOMOS_SERVER}/api/hooks/${AUTONOMOS_SESSION_ID}"' +
" >/dev/null 2>&1 &";
const hookEntry = {
matcher: "",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Suggestion

Problem: matcher: "" for PreToolUse/PostToolUse is non-obvious — an empty-string glob typically matches nothing in most pattern libraries, so it's unclear whether this intentionally means "match all tools" or is a quirk of how Claude Code resolves the pattern.

Why it matters: If "" is ever changed to a stricter match (e.g. after a Claude Code update), tool-use hooks silently stop firing and dashboard status icons stop updating for those events. No error, no log.

Suggested fix:

const hookEntry = {
  // Empty string matches all tools in CC hook resolution (empirically verified).
  // Use "*" if CC ever adds explicit glob support to make the intent clear.
  matcher: "",
  hooks: [{ type: "command", command: hookCmd, timeout: 3, async: true }],
};

Just a one-liner comment to document the intent — makes the next reader (or future-you) confident this is deliberate.

@nox-0x nox-0x left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, well-executed refactor — the previous warnings (~ path expansion, writeFileSync atomicity) are fully addressed by switching to inline --settings. The zero-file, zero-config approach is strictly better than the old hook installer. One suggestion left on matcher: "" for PreToolUse/PostToolUse: just add a comment clarifying that empty-string is intentional and empirically verified, so future-you (or a CC update) does not silently break tool-use events. No blocking issues — good to merge.

@aterrylu
aterrylu merged commit d957f32 into main Mar 24, 2026
1 check passed
@aterrylu
aterrylu deleted the terry/auto-install-hooks branch March 24, 2026 08:14
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.

2 participants