Skip to content

Add a managed hook-entry utility for Claude Code settings.json #1007

Description

@williamthorsen

Problem

CodeAssembly needs to install hook entries into Claude Code's ~/.claude/settings.json, but the file is user-curated — dotfiles-managed on some machines, carrying unrelated settings and foreign hook entries. No utility exists to add, verify, or remove CodeAssembly-owned entries without endangering the rest of the file.

Context

  • settings.json is strict JSON, so comment-based sentinels are unavailable — and Claude Code validates user settings strictly against a schema with additionalProperties: false on hook objects, rejecting a failing file as a whole. Ownership is therefore marked by a sentinel token carried in a hook command string, not a metadata key on the entry. A sentinel-matching entry (one element of a hooks.{Event} array whose inner hooks[].command contains the token) is wholly CodeAssembly's — replaced or removed as a unit; nothing else is touched.
  • Sibling ticket: Add a managed event-hook utility for Rovo Dev config.yml #1008 manages Rovo Dev's config.yml. Both utilities expose the same ensure/check/remove contract so the harness wiring in Emit session-lifecycle events via harness hooks #1005 stays uniform per harness. Shared contract shape, independent implementations.
  • Consumed by Emit session-lifecycle events via harness hooks #1005, which owns the actual hook entries, relay script, CLI wiring, and docs. This utility stays agnostic about what is injected.

Proposed solution

A library module in packages/agents exposing three operations over caller-supplied hook entries and a sentinel token:

  • ensure — merge entries into the appropriate hooks.{Event} arrays, creating missing structure (including a missing file); a sentinel-matching entry that differs is replaced in place, never duplicated. A supplied entry that does not itself carry the sentinel is rejected — ensure must never write an entry it cannot later find.
  • check — report each supplied entry as present, drifted (a sentinel-matching entry whose content differs from the supplied entry), or absent, for status-style reporting.
  • remove — delete sentinel-matching entries only, taking just the sentinel (not an entry list) so it also cleans up entries written by earlier versions; prunes structure the removal emptied.

Acceptance criteria

Must have

  • Ensure is idempotent: an immediate re-run is a no-op, and a drifted sentinel-matching entry is replaced rather than duplicated.
  • Ensure rejects a supplied entry that does not carry the sentinel, without writing.
  • Foreign hook entries and unrelated settings keys survive every operation unchanged.
  • Remove deletes only sentinel-matching entries and prunes structure emptied by the removal.
  • A file that cannot be parsed is never written to; the failure is reported to the caller.
  • The exposed contract shape matches the Rovo utility's (Add a managed event-hook utility for Rovo Dev config.yml #1008).
  • New behavior is covered by tests.

Should have

  • The file's existing indentation style is preserved.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions