Skip to content

doctor: hook-safety suite (sprawl, binaries, network, duplicates)#75

Merged
vishnujayvel merged 2 commits into
mainfrom
feat/doctor-hook-safety
Jun 13, 2026
Merged

doctor: hook-safety suite (sprawl, binaries, network, duplicates)#75
vishnujayvel merged 2 commits into
mainfrom
feat/doctor-hook-safety

Conversation

@vishnujayvel

@vishnujayvel vishnujayvel commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a hook-safety suite to hookwise doctor — the highest-leverage defense against the hook-overload incident class (37 hooks / 12 events caused a 2-hour outage). Closes #33, #34, #35, #36.

New internal/hooks package: a hermetic Claude Code settings scanner + four pure analyses, all TDD'd. Wired into doctor as "Check 7".

Checks (each matches its issue's exact output format)

Issue Check Behavior
#34 inventory + sprawl SCAN hooks: N across M events + per-event breakdown; WARN/FAIL when a hot-path event (PreToolUse/PostToolUse) exceeds 5/10 or other events exceed 3/8
#33 missing binary extracts the executable from each hook command, checks PATH via exec.LookPath; FAIL with the count of dependent hooks
#35 network on hot path flags uvx/uv run/npx/pip install/curl/wget/docker run only on PreToolUse/PostToolUse; docker run --pull=never is exempt
#36 duplicates + overlap exact-duplicate WARN (same command+event); functional-overlap INFO when multiple known guard systems (hookwise dispatch, claude-code-guardian, skill-routing-guard) share an event

Real-world smoke (on the maintainer's own settings)

SCAN  hooks: 23 hooks across 5 events
        PreToolUse:    12 hooks
        ...
FAIL  hook-sprawl: PreToolUse has 12 hooks (threshold: 10)
WARN  hook-duplicate: "...calendar_guard.py" appears 4 times on PreToolUse

It immediately surfaced genuine sprawl + a 4× duplicated guard — the feature works.

Design notes

  • Scanner is hermetic (reads files, no network) → fully unit-testable. Missing/malformed settings files are tolerated (recorded, not fatal).
  • exec.LookPath is injected so the binary check is testable without touching the real PATH.
  • Scans ~/.claude/settings.json + ~/.claude/projects/*/settings.local.json.
  • internal/hooks added to the guarded task test:go:unit target (retro-009: no bare go test).

Verification

  • TDD red→green for every analysis; task test:go:unit + test:go:contract + test:go:arch all green.
  • Contract parity (ARCH-6) preserved — dispatch fixtures untouched.
  • task install + hookwise doctor smoked on real data.

Stream B of the post-Dolt backlog (B→C→D). Out of scope: the untracked working-tree files.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • The hookwise doctor command now performs comprehensive hook safety checks, including inventory analysis, sprawl detection, missing binary detection, network dependency scanning, and duplicate identification.
  • Tests

    • Added unit tests for hook analysis functionality.
  • Chores

    • Updated build task configuration to include hook analysis tests.

Adds a new internal/hooks package (settings scanner + analyses) and wires four
checks into `hookwise doctor`, closing #33/#34/#35/#36:

- #34 inventory scan: SCAN line with total + per-event breakdown; sprawl
  WARN/FAIL thresholds (hot-path PreToolUse/PostToolUse >5/>10, others >3/>8).
- #33 missing binary: extract the executable from each hook command and check
  PATH (exec.LookPath, injected for hermetic tests); FAIL with dependent count.
- #35 network hooks: flag uvx/uv run/npx/pip install/curl/wget/docker run on
  hot-path events only; docker --pull=never is exempt.
- #36 duplicates + overlap: exact-duplicate WARN; functional-overlap INFO when
  multiple known guard systems share an event.

Scanner reads ~/.claude/settings.json + projects/*/settings.local.json, tolerant
of missing/malformed files. internal/hooks added to the guarded test target.

TDD throughout; unit + contract + arch gates green. Smoked on real settings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@vishnujayvel, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 51 minutes and 56 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f4171487-24c4-48b3-8dd6-833f1849e43e

📥 Commits

Reviewing files that changed from the base of the PR and between 71eeb64 and 86889c1.

📒 Files selected for processing (2)
  • cmd/hookwise/cmd_doctor.go
  • internal/hooks/paths_test.go
📝 Walkthrough

Walkthrough

This PR introduces a hook safety analysis subsystem to the hookwise doctor command. It adds hook scanning, inventory parsing, and finding generation across five analysis categories (inventory, sprawl, missing binaries, network runners, duplicates), wires them into doctor Check 7, and includes comprehensive unit test coverage with test suite integration.

Changes

Hook Safety Analysis & Doctor Integration

Layer / File(s) Summary
Hook scanning and inventory
internal/hooks/scan.go, internal/hooks/scan_test.go, internal/hooks/paths.go, internal/hooks/paths_test.go
Scan(paths) reads JSON settings files, parses hook entries, and tolerates missing/malformed files. SettingsPaths discovers global and project-local settings files with sorted project paths. DefaultSettingsPaths targets ~/.claude. Tests validate multi-file accumulation, missing file tolerance, and parse error handling.
Hook analysis and finding generation
internal/hooks/analyze.go, internal/hooks/analyze_test.go
Five analysis functions generate structured findings: InventoryFinding totals hooks per event, SprawlFindings warns/fails on excessive hooks per event (hot-path aware), MissingBinaryFindings fails on binaries not found via injected lookPath, NetworkHookFindings warns on network-dependent runners (with docker run --pull=never exception), and DuplicateFindings detects exact command duplicates and guard-system overlap. Tests cover thresholds, filtering, binary resolution, and pattern matching.
Doctor command Check 7 integration
cmd/hookwise/cmd_doctor.go, Taskfile.yml
checkHookSafety scans settings via hooks.AllFindings, renders each finding by level/details, and counts warnings/failures. renderHookFinding formats output based on severity. Doctor Check 7 calls checkHookSafety and aggregates results into overall health. Taskfile adds ./internal/hooks/... to unit test run.

Sequence Diagram(s)

sequenceDiagram
    participant Doctor as runDoctor
    participant CheckHook as checkHookSafety
    participant Scan as hooks.Scan
    participant Analyze as hooks.AllFindings
    participant Render as renderHookFinding
    Doctor->>CheckHook: execute Check 7
    CheckHook->>Scan: Scan(DefaultSettingsPaths)
    Scan-->>CheckHook: *Inventory
    CheckHook->>Analyze: AllFindings(inv, nil)
    Analyze-->>CheckHook: []Finding
    loop each Finding
        CheckHook->>Render: renderHookFinding(w, f)
        Render-->>CheckHook: formatted output
    end
    CheckHook-->>Doctor: warnings, fails
    Doctor->>Doctor: aggregate with other checks
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • #33: feat: doctor — detect missing hook binaries on PATH — The PR implements MissingBinaryFindings with injected lookPath callback to verify hook command binaries exist on PATH, directly fulfilling the binary existence check requirement.
  • #34: Hook sprawl detection — The PR implements SprawlFindings with hot-path-aware WARN/FAIL thresholds for excessive hooks per event, directly fulfilling the sprawl detection feature.
  • #36: Duplicate/redundancy detection — The PR implements DuplicateFindings with exact command duplicate and guard-system overlap detection, directly fulfilling the duplicate detection requirements.
  • #38: Standalone hookwise audit command — The PR introduces hooks.AllFindings and all analysis functions that would form the core of a separate audit command with the same scanning and finding-generation logic.
  • #39: Scan existing hooks during init — The PR introduces hooks.Scan and hooks.SettingsPaths that read and inventory hooks from Claude Code settings, providing the scanning foundation needed for hook initialization workflow.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 59.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main change: introducing a hook-safety suite with four specific analysis features (sprawl, binaries, network, duplicates) to the doctor command.
Description check ✅ Passed The PR description is comprehensive, follows the template structure with Summary, Motivation (Closes issues), and Test plan sections completed. All required information is present and detailed.
Linked Issues check ✅ Passed The PR implementation fully addresses issue #33 requirements: reads settings files, extracts binary names, verifies via exec.LookPath, reports missing binaries with hook counts, and includes comprehensive unit tests. All acceptance criteria are met.
Out of Scope Changes check ✅ Passed All changes are scoped to the hook-safety suite requirements: new internal/hooks package with scanner and analyses, Taskfile update for test coverage, and doctor command integration. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/doctor-hook-safety

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/hookwise/cmd_doctor.go`:
- Line 169: Replace the ignored error from hooks.Scan by capturing and handling
it: change the call to inv, err := hooks.Scan(hooks.DefaultSettingsPaths()) and
then check err; if not nil, return a wrapped error (e.g. return
fmt.Errorf("scanning hooks settings: %w", err)) or log and exit appropriately
for the surrounding command handler. This uses the hooks.Scan and
hooks.DefaultSettingsPaths symbols and ensures the command surfaces any future
Scan failures instead of silently discarding them.

In `@internal/hooks/analyze.go`:
- Around line 211-214: The current check that treats commands containing
"--pull=never" as safe is vulnerable to substring false-positives; update the
guard around the condition that inspects e.Command so it tokenizes the command
(e.g., via strings.Fields(e.Command)) and checks for an exact token equal to
"--pull=never" (or use a boundary-aware regex like `(^|\\s)--pull=never(\\s|$)`)
instead of strings.Contains; modify the if that currently reads `if
strings.Contains(e.Command, "docker run") && strings.Contains(e.Command,
"--pull=never") { continue }` to use the tokenized/regex check for the flag
while keeping the existing "docker run" check.

In `@internal/hooks/paths_test.go`:
- Line 19: The test creates a temporary directory with overly permissive mode
0o755; change the call using os.MkdirAll(dir, 0o755) to use a stricter
permission like 0o750 (or 0750) to satisfy gosec and follow defensive practices,
ensuring tests still run (owner read/write/execute and group read/execute only).

In `@internal/hooks/paths.go`:
- Line 19: The filepath.Glob call currently discards its error (matches, _ :=
filepath.Glob(...)), which can hide permission or filesystem problems; change it
to capture the error (matches, err := filepath.Glob(filepath.Join(claudeDir,
"projects", "*", "settings.local.json"))), and if err != nil record or log it
for observability—either append the error to the same Scan.ParseErrors
collection used elsewhere or emit a clear log entry so callers know the glob
failed; keep the existing behavior of using matches when err==nil.

In `@internal/hooks/scan.go`:
- Around line 54-68: The doc comment for Scan is inaccurate: the implementation
reads files with os.ReadFile, records non-IsNotExist read failures into
Inventory.ParseErrors (using ParseError) and always returns a nil error; update
the comment on Scan to state that Scan is best-effort, that missing files are
skipped, and that any unreadable-but-existing file errors are recorded in
Inventory.ParseErrors (ParseError) rather than being returned as a fatal error;
leave the implementation of Scan, Inventory.ParseErrors, and ParseError
unchanged.
- Around line 59-95: The code comment and behavior for Scan() diverge: Scan
currently accumulates unreadable/existing-file failures into
Inventory.ParseErrors (using ParseError) but always returns nil error; update
Scan to return a non-nil error when there are recorded ParseErrors. Concretely,
in Scan (after building inv) check if len(inv.ParseErrors) > 0 and return inv
plus an aggregated error (e.g., build a single error string or wrap the first
ParseError) instead of nil so callers can detect parse/read failures; keep using
Inventory, ParseErrors and ParseError types for details in the returned
Inventory.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 124146d7-bd9c-4fdf-86ad-dfefddb5bbf0

📥 Commits

Reviewing files that changed from the base of the PR and between 1218be9 and 71eeb64.

📒 Files selected for processing (8)
  • Taskfile.yml
  • cmd/hookwise/cmd_doctor.go
  • internal/hooks/analyze.go
  • internal/hooks/analyze_test.go
  • internal/hooks/paths.go
  • internal/hooks/paths_test.go
  • internal/hooks/scan.go
  • internal/hooks/scan_test.go

Comment thread cmd/hookwise/cmd_doctor.go Outdated
Comment thread internal/hooks/analyze.go
Comment on lines +211 to +214
// "docker run --pull=never" never pulls — treat as safe.
if strings.Contains(e.Command, "docker run") && strings.Contains(e.Command, "--pull=never") {
continue
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there are any real-world docker run commands in the codebase that might test this edge case
rg -n 'docker run.*--pull' --type=go

Repository: vishnujayvel/hookwise

Length of output: 498


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Broader search for the exact flag and potential near-miss variants
rg -n --hidden --no-ignore --glob '!*node_modules/*' --glob '!*dist/*' --glob '!*build/*' --glob '!*vendor/*' --glob '!*target/*' \
  -- '--pull=never' .

rg -n --hidden --no-ignore --glob '!*node_modules/*' --glob '!*dist/*' --glob '!*build/*' --glob '!*vendor/*' --glob '!*target/*' \
  -- '--pull=never-[A-Za-z0-9_-]+' .

# Inspect how e.Command is obtained/normalized and confirm snippet context
sed -n '180,240p' internal/hooks/analyze.go

sed -n '90,170p' internal/hooks/analyze_test.go

Repository: vishnujayvel/hookwise

Length of output: 506


🏁 Script executed:

#!/bin/bash
set -u

# 1) Broader searches (don’t fail the script if there are no matches)
rg -n --hidden --no-ignore \
  --glob '!*node_modules/*' --glob '!*dist/*' --glob '!*build/*' --glob '!*vendor/*' --glob '!*target/*' \
  -- '--pull=never' . || true

rg -n --hidden --no-ignore \
  --glob '!*node_modules/*' --glob '!*dist/*' --glob '!*build/*' --glob '!*vendor/*' --glob '!*target/*' \
  -- '--pull=never-' . || true

# 2) Inspect the exact snippet context and how e.Command is formed/normalized
sed -n '190,240p' internal/hooks/analyze.go
sed -n '110,160p' internal/hooks/analyze_test.go

Repository: vishnujayvel/hookwise

Length of output: 3995


Tighten --pull=never detection to avoid substring false-positives. (internal/hooks/analyze.go ~211-214)

The current guard uses strings.Contains(e.Command, "--pull=never"), and there’s a test for the exact "docker run --pull=never img" case. Substring matching can still treat tokens like --pull=never-mind as safe. Consider matching the flag as an argument token (e.g., boundary-aware regex like (^|\\s)--pull=never(\\s|$) or parsing strings.Fields and comparing exact args).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/hooks/analyze.go` around lines 211 - 214, The current check that
treats commands containing "--pull=never" as safe is vulnerable to substring
false-positives; update the guard around the condition that inspects e.Command
so it tokenizes the command (e.g., via strings.Fields(e.Command)) and checks for
an exact token equal to "--pull=never" (or use a boundary-aware regex like
`(^|\\s)--pull=never(\\s|$)`) instead of strings.Contains; modify the if that
currently reads `if strings.Contains(e.Command, "docker run") &&
strings.Contains(e.Command, "--pull=never") { continue }` to use the
tokenized/regex check for the flag while keeping the existing "docker run"
check.

Comment thread internal/hooks/paths_test.go Outdated
Comment thread internal/hooks/paths.go
func SettingsPaths(claudeDir string) []string {
paths := []string{filepath.Join(claudeDir, "settings.json")}

matches, _ := filepath.Glob(filepath.Join(claudeDir, "projects", "*", "settings.local.json"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider logging glob errors for observability.

The error from filepath.Glob is silently discarded. While the pattern is hardcoded and valid, Glob can also return errors for permission issues or filesystem problems. Silently continuing with zero project settings might mask permission problems that the user would want to know about.

Consider recording the glob error in a similar way to how Scan records ParseErrors, or at minimum logging it for diagnostics.

💡 Optional improvement

If the Inventory structure allowed it, you could record glob errors:

 func SettingsPaths(claudeDir string) []string {
 	paths := []string{filepath.Join(claudeDir, "settings.json")}
 
-	matches, _ := filepath.Glob(filepath.Join(claudeDir, "projects", "*", "settings.local.json"))
+	matches, err := filepath.Glob(filepath.Join(claudeDir, "projects", "*", "settings.local.json"))
+	if err != nil {
+		// Log or record the error for diagnostics
+	}
 	sort.Strings(matches)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/hooks/paths.go` at line 19, The filepath.Glob call currently
discards its error (matches, _ := filepath.Glob(...)), which can hide permission
or filesystem problems; change it to capture the error (matches, err :=
filepath.Glob(filepath.Join(claudeDir, "projects", "*",
"settings.local.json"))), and if err != nil record or log it for
observability—either append the error to the same Scan.ParseErrors collection
used elsewhere or emit a clear log entry so callers know the glob failed; keep
the existing behavior of using matches when err==nil.

Comment thread internal/hooks/scan.go
Comment on lines +54 to +68
// Scan reads each settings file path in order and returns the combined hook
// inventory. Missing files are silently skipped; malformed files are recorded
// in Inventory.ParseErrors and do not abort the scan. The only error Scan
// returns is for an unreadable (but existing) file's non-IsNotExist failure —
// callers can otherwise treat the inventory as best-effort.
func Scan(paths []string) (*Inventory, error) {
inv := &Inventory{}
for _, p := range paths {
data, err := os.ReadFile(p)
if err != nil {
if os.IsNotExist(err) {
continue // a missing settings file is normal
}
inv.ParseErrors = append(inv.ParseErrors, ParseError{File: p, Err: err})
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Inconsistency: function doc vs. implementation on error handling.

The doc comment (lines 56-58) states: "The only error Scan returns is for an unreadable (but existing) file's non-IsNotExist failure". However, the implementation at lines 67-68 records these failures as ParseError entries instead of returning them as the function's error return value.

Since Scan always returns nil error (line 94), the doc is misleading. Either:

  • Update the doc to reflect that all failures are non-fatal and recorded in Inventory.ParseErrors, or
  • Change the implementation to return non-IsNotExist read errors as fatal errors.

Given the fail-safe design mentioned in the PR objectives, the current implementation seems intentional, so updating the doc is likely the right fix.

📝 Proposed doc fix
-// Scan reads each settings file path in order and returns the combined hook
-// inventory. Missing files are silently skipped; malformed files are recorded
-// in Inventory.ParseErrors and do not abort the scan. The only error Scan
-// returns is for an unreadable (but existing) file's non-IsNotExist failure —
-// callers can otherwise treat the inventory as best-effort.
+// Scan reads each settings file path in order and returns the combined hook
+// inventory. Missing files are silently skipped; malformed files and read
+// errors are recorded in Inventory.ParseErrors and do not abort the scan.
+// Scan always succeeds (returns nil error); callers should check ParseErrors
+// for diagnostics.
 func Scan(paths []string) (*Inventory, error) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Scan reads each settings file path in order and returns the combined hook
// inventory. Missing files are silently skipped; malformed files are recorded
// in Inventory.ParseErrors and do not abort the scan. The only error Scan
// returns is for an unreadable (but existing) file's non-IsNotExist failure —
// callers can otherwise treat the inventory as best-effort.
func Scan(paths []string) (*Inventory, error) {
inv := &Inventory{}
for _, p := range paths {
data, err := os.ReadFile(p)
if err != nil {
if os.IsNotExist(err) {
continue // a missing settings file is normal
}
inv.ParseErrors = append(inv.ParseErrors, ParseError{File: p, Err: err})
continue
// Scan reads each settings file path in order and returns the combined hook
// inventory. Missing files are silently skipped; malformed files and read
// errors are recorded in Inventory.ParseErrors and do not abort the scan.
// Scan always succeeds (returns nil error); callers should check ParseErrors
// for diagnostics.
func Scan(paths []string) (*Inventory, error) {
inv := &Inventory{}
for _, p := range paths {
data, err := os.ReadFile(p)
if err != nil {
if os.IsNotExist(err) {
continue // a missing settings file is normal
}
inv.ParseErrors = append(inv.ParseErrors, ParseError{File: p, Err: err})
continue
🧰 Tools
🪛 golangci-lint (2.12.2)

[medium] 62-62: G304: Potential file inclusion via variable

(gosec)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/hooks/scan.go` around lines 54 - 68, The doc comment for Scan is
inaccurate: the implementation reads files with os.ReadFile, records
non-IsNotExist read failures into Inventory.ParseErrors (using ParseError) and
always returns a nil error; update the comment on Scan to state that Scan is
best-effort, that missing files are skipped, and that any
unreadable-but-existing file errors are recorded in Inventory.ParseErrors
(ParseError) rather than being returned as a fatal error; leave the
implementation of Scan, Inventory.ParseErrors, and ParseError unchanged.

Comment thread internal/hooks/scan.go
Addresses CodeRabbit nitpicks on #75: handle hooks.Scan's error defensively
and render a WARN for each malformed settings file (doctor now flags an
unparseable settings.json instead of silently skipping it); tighten a test
tmp-dir to 0o750 (gosec).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant