fix(openfactor): skip tool hooks when no orchestration daemon listens

Merged
SH
19d ago
+90-1

Summary

  • After openfactor hooks install, activation reads each host receipt's configPath. In each JSON hook config it wraps every non-prompt openfactor <harness>-hook <Event> command as sh -c 'test -S "<daemon socket>" || exit 0; exec openfactor ...'.
  • The generated OpenCode plugin gets the same existsSync(<daemon socket>) check before it spawns the CLI.
  • The socket path matches the CLI's own: ${OPENFACTOR_ORCHESTRATION_SOCKET:-${OPENFACTOR_ORCHESTRATION_STATE:-.orchestration-state}/daemon.sock}.

Why

On matic each PreToolUse/PostToolUse hook started the ~90 MB Bun CLI. Each start cost 0.67-0.75 s of user CPU and 0.37-0.42 s of wall time, and spiked to about 196% CPU per call across parallel tool calls. No orchestration daemon socket exists on the host, so the CLI posts nothing and exits. That was pure overhead on every tool call from every harness.

decided: gate in the activation wrapper rather than drop the hooks - lanes that run the daemon still get every event
decided: leave prompt events ungated - the CLI records lane prompt receipts from them without a daemon
decided: keep openfactor in the gated command string - hooks install treats an entry naming openfactor as installed, so re-runs stay idempotent instead of appending a second hook

Test plan

  • shellspec spec/openfactor_spec.sh (11 examples): gates tool hooks, leaves prompt and foreign hooks unchanged, re-run is idempotent, CLI runs only when the socket exists, OpenCode plugin is gated
  • shellcheck on install.sh and the spec
  • nixfmt --check config/openfactor/default.nix

🤖 Generated with Claude Code


Summary by cubic

Tool hooks installed by openfactor hooks install now check for the orchestration daemon socket before invoking the CLI, so tool calls outside orchestrated lanes no longer boot the ~90 MB Bun CLI just to drop the event—each start cost about 0.7 CPU-seconds per call.

  • Wraps every non-prompt openfactor <harness>-hook <Event> command in sh -c 'test -S "<daemon socket>" || exit 0; exec openfactor ...'; prompt hooks stay ungated because the CLI records lane prompt receipts without a daemon.
  • The generated OpenCode plugin gets the same existsSync gate before spawning the CLI.
  • The gated command keeps openfactor in its name, so re-running hooks install stays idempotent instead of appending a second hook.
  • config/openfactor/default.nix adds pkgs.jq, used by activation to read each host receipt's configPath.