Skip to content

[Refactor] hooks should degrade gracefully when jq is unavailable #280

@Ref34t

Description

@Ref34t

Driver

Many ApexYard hooks (counted 22 in the current .claude/hooks/ shipped with v1.2.0) use jq to read .claude/project-config.json for adopter overrides — .ui_paths, .git.protected_branches, .ticket.required_sections, .migration_paths, and others.

jq is not declared as a dependency anywhere in docs/getting-started.md, the /setup skill, or the install README. On a fresh-machine adopter who hasn't independently installed jq (e.g. a fresh macOS install without Homebrew set up for jq), the override reads silently fail — jq -r '...' 2>/dev/null returns empty, the hook falls back to its default array, and the adopter's overrides have no effect. No error, no warning, no log line.

Surfaced on 2026-05-18 in an adopter fork where a .ui_paths override was added to fix a design-review-gate false positive on a non-UI .jsx file. The override didn't take effect because jq wasn't installed. The hook silently kept matching every .jsx file as UI. The adopter only discovered the issue by tracing why the override appeared to have no effect.

This is a class problem, not a one-off — every hook that reads project-config silently degrades on jq-less machines.

Scope

Two acceptable fixes (the choice is up to apexyard maintainers):

Option A — declare and check jq as a hard dependency:

  • /setup skill detects command -v jq; refuses with a clear error + install instructions if missing
  • docs/getting-started.md lists jq in the prerequisites section
  • A SessionStart hook warns once if jq is missing (similar to the upstream-drift banner)

Option B — implement a jq-free fallback inside _lib-read-config.sh:

  • Try jq first (fast, well-tested)
  • Fall back to a python one-liner (python3 -c "import json, sys; ...") or a POSIX-compatible JSON parser
  • Each hook continues to call config_get '.some.key' — the lib handles whichever path is available
  • Hard error only if BOTH jq and python3 are missing (extremely unusual)

Option B is more invasive but doesn't shift dependency-management burden to adopters. Option A is simpler.

Out of scope: rewriting hooks to use a different JSON parsing library; changing the project-config schema.

Acceptance Criteria

  • On a system without jq installed, an adopter's .ui_paths (or any other project-config override) takes effect on the merge gate
  • OR: on a system without jq, the operator gets a clear, single, visible warning surfaced via /setup or a SessionStart hook — not silent degradation
  • Existing systems with jq continue to work unchanged
  • Documentation (docs/getting-started.md or equivalent) reflects whichever path is chosen

Risks / Dependencies

  • Risk: Option B (Python fallback) adds a second dependency. Mitigation: Python 3 is on every macOS, every modern Linux distro, and present in most CI images. Acceptable.
  • Risk: Option A (jq-as-hard-dep) blocks adopters on systems that can't easily install jq (locked-down corporate environments). Mitigation: very few in apexyard's target audience; the docs path remains.
  • Risk: behaviour change for adopters who were inadvertently relying on the silent-fallback (their overrides were never being honoured, but they didn't know). Mitigation: this is the right behaviour change — they should know.

Surfaced during an adopter fork's design-review-gate false-positive triage on 2026-05-18.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions