Skip to content

Add: --install-hook for auto-reapply after CC updates#654

Open
LeonFedotov wants to merge 5 commits intoPiebald-AI:mainfrom
LeonFedotov:feat/auto-reapply-hook
Open

Add: --install-hook for auto-reapply after CC updates#654
LeonFedotov wants to merge 5 commits intoPiebald-AI:mainfrom
LeonFedotov:feat/auto-reapply-hook

Conversation

@LeonFedotov
Copy link
Copy Markdown

@LeonFedotov LeonFedotov commented Apr 1, 2026

Summary

Adds CLI options to install a Claude Code SessionStart hook that automatically reapplies tweakcc patches after CC auto-updates. Addresses #101.

  • --install-hook — installs hook in ~/.claude/settings.json
  • --remove-hook — removes the hook
  • --quiet — suppresses output in --apply mode (for hook usage)

Motivation

Claude Code auto-updates replace the binary, wiping all tweakcc patches. Users must manually re-run tweakcc --apply after every update. This has been reported as the biggest pain point (#101) and the maintainer has expressed interest in solving it.

The solution leverages Claude Code's own SessionStart hook system — a shell command that runs at the start of every CC session. The hook detects the installation and reapplies patches silently.

How it works

# One-time setup
tweakcc --install-hook

This writes to ~/.claude/settings.json:

{
  "hooks": {
    "SessionStart": [{
      "matcher": "",
      "hooks": [{
        "type": "command",
        "command": "tweakcc --apply --quiet",
        "timeout": 30
      }]
    }]
  }
}

On every CC session start:

  1. Hook runs tweakcc --apply --quiet
  2. tweakcc detects the CC installation, backs up if version changed, applies all patches
  3. --quiet suppresses all output so session startup isn't polluted
  4. Errors are swallowed (non-zero exit doesn't block CC)
# To remove
tweakcc --remove-hook

Changes

File Change
src/index.tsx --install-hook, --remove-hook, --quiet options + handleHookMode()

Commits

  1. Initial implementation--install-hook, --remove-hook, --quiet flags with handleHookMode()
  2. Conflict handling + hooks validation — fail fast on --install-hook --remove-hook, validate hooks/SessionStart structure with Array.isArray guards
  3. Precise hook matching — exact command === match instead of includes(), per-hook splice on remove instead of deleting the whole entry
  4. ENOENT vs permission errors — outer catch distinguishes file-not-found from EACCES/EPERM
  5. Flatten settings.json error handling — single try/catch that cleanly distinguishes ENOENT, SyntaxError, non-object JSON, and permission errors

Edge cases handled

  • First run (no settings.json): Creates the file with just the hook
  • Existing settings.json: Preserves all existing settings, only adds/removes the hook entry
  • Invalid JSON: Exits with error, never overwrites corrupt settings
  • Non-object JSON (null, []): Exits with descriptive error
  • Unreadable file (EACCES/EPERM): Exits with the actual error message
  • Already installed: --install-hook is idempotent — reports "already installed"
  • Not found: --remove-hook when no hook exists — reports "not found"
  • Mixed hooks: Remove only deletes the tweakcc hook, preserves other hooks in the same entry
  • Concurrent sessions: --apply is deterministic (same config → same patches), so races are benign
  • Global vs npx install: Hook uses tweakcc (not npx tweakcc) to avoid download latency. Requires global install (npm i -g tweakcc).

Testing

  • pnpm lint passes
  • pnpm run test passes (221 total, 0 failures)
  • pnpm prettier --check src passes

Relates to: #101

Summary by CodeRabbit

  • New Features
    • Added --install-hook and --remove-hook CLI flags to add or remove an automatic session-start hook in user settings.
    • Added -q/--quiet flag to suppress informational output while still performing apply operations.
  • Bug Fixes
    • Validates arguments so install/remove flags cannot be combined and take precedence over normal execution.
    • Hook install/update avoids duplicating identical entries; removal cleans up empty hook structures.

Adds three new CLI options:
  --install-hook   Install a SessionStart hook in ~/.claude/settings.json
                   that runs `tweakcc --apply --quiet` on every CC session start
  --remove-hook    Remove the auto-reapply hook
  --quiet          Suppress output in --apply mode (for hook usage)

This addresses the long-standing issue of CC auto-updates wiping patches,
requiring users to manually re-run tweakcc after every update.

The hook runs `tweakcc --apply --quiet` which:
- Detects the current CC installation
- Reapplies all saved customizations
- Suppresses output so it doesn't pollute session startup

Relates to: Piebald-AI#101
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds CLI flags --install-hook, --remove-hook, and -q/--quiet. Implements hook management that reads/writes ~/.claude/settings.json SessionStart hooks to add/remove a tweakcc --apply [--quiet] command. Threads quiet into apply flow to suppress most logging and patch printing in non-interactive mode.

Changes

Cohort / File(s) Summary
CLI entry / Hook & Quiet logic
src/index.tsx
Added --install-hook, --remove-hook, and -q/--quiet flags. Implemented handleHookMode(install: boolean) to read/modify ~/.claude/settings.json SessionStart hooks (add/remove tweakcc --apply [--quiet]). Enforced mutual exclusion between install/remove, short-circuited main to run hook handling, updated handleApplyMode(quiet: boolean) and suppressed logging/patch output when quiet is set.

Sequence Diagram(s)

sequenceDiagram
  participant User as "User (CLI)"
  participant CLI as "tweakcc CLI"
  participant FS as "Filesystem (~/.claude/settings.json)"
  participant Claude as "Claude Hooks (SessionStart)"

  User->>CLI: invoke with --install-hook / --remove-hook / --apply [-q]
  alt install/remove hook flow
    CLI->>FS: read settings.json
    FS-->>CLI: return JSON
    CLI->>Claude: inspect/create `SessionStart` hooks entry
    CLI->>FS: write updated settings.json (add/remove `tweakcc --apply [--quiet]`)
    FS-->>CLI: write result
    CLI-->>User: exit (messages suppressed if -q)
  else apply (non-interactive) flow
    CLI->>CLI: run handleApplyMode(quiet)
    alt quiet = false
      CLI-->>User: print patch results and statuses
    else quiet = true
      CLI-->>User: minimal output (suppress logs/patch printing)
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • bl-ue

Poem

🐰 I hop through settings, tidy and bright,
Quietly adding hooks in the night.
A tweak, a whisper, a patch made light,
Then I burrow home — all calm and right.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main feature being added: the --install-hook CLI flag for automatic patch reapplication after Claude Code updates.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/index.tsx`:
- Around line 370-374: The code assumes settings.hooks and
settings.hooks.SessionStart are arrays and casts them directly to sessionStart,
which can throw when the shape is different; update the initialization of
hooks/sessionStart (symbols: hooks, sessionStart, settings.hooks, SessionStart)
to first check types using Array.isArray and guard the inner items' shape
(ensure each item is an object with optional matcher and hooks arrays) and
fallback to [] when invalid, and likewise guard any subsequent array ops (e.g.,
findIndex, some) by validating the target is an array before calling those
methods so malformed user settings cannot cause runtime errors.
- Around line 363-368: The try/catch around reading/parsing settings
(fsSync.readFileSync(settingsPath) and JSON.parse) currently treats parse errors
the same as a missing file and can overwrite user settings; change the catch to
distinguish a missing file (ENOENT) from a JSON parse error: if the file is
missing, initialize settings to an empty object; if JSON.parse throws, log the
parse error and leave the existing settings variable untouched (do not replace
it with a new minimal object or write out a new settings.json). Apply the same
fix to the other read/parse block mentioned (the block around lines 405-410) so
parse failures never overwrite existing settings.
- Around line 236-240: The CLI currently treats --install-hook and --remove-hook
together as install mode; update the handling around
options.installHook/options.removeHook so when both are truthy it fails fast:
detect the conflict, log or throw a clear error (or call process.exit with
non-zero) indicating the flags cannot be used together, and only call await
handleHookMode(!!options.installHook) when exactly one of options.installHook or
options.removeHook is set; reference the existing options.installHook,
options.removeHook, and handleHookMode to locate and modify the logic.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2b9f16f5-db12-41d8-bd67-c6fad7f32869

📥 Commits

Reviewing files that changed from the base of the PR and between bc5cdbe and 44cfb05.

📒 Files selected for processing (1)
  • src/index.tsx

Comment thread src/index.tsx
Comment thread src/index.tsx
Comment on lines +363 to +368
try {
const raw = fsSync.readFileSync(settingsPath, 'utf8');
settings = JSON.parse(raw) as Record<string, unknown>;
} catch {
// File doesn't exist or isn't valid JSON — start fresh
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not overwrite Claude settings when JSON parsing fails.

At Line 366-Line 368, invalid JSON is treated the same as a missing file. In install mode, this can replace an existing settings.json with a new minimal object and drop unrelated user settings.

Suggested fix
-  } catch {
-    // File doesn't exist or isn't valid JSON — start fresh
+  } catch (error) {
+    const err = error as NodeJS.ErrnoException;
+    if (err.code !== 'ENOENT') {
+      console.error(chalk.red(`Error reading ${settingsPath}: ${err.message}`));
+      process.exit(1);
+    }
   }

Also applies to: 405-410

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/index.tsx` around lines 363 - 368, The try/catch around reading/parsing
settings (fsSync.readFileSync(settingsPath) and JSON.parse) currently treats
parse errors the same as a missing file and can overwrite user settings; change
the catch to distinguish a missing file (ENOENT) from a JSON parse error: if the
file is missing, initialize settings to an empty object; if JSON.parse throws,
log the parse error and leave the existing settings variable untouched (do not
replace it with a new minimal object or write out a new settings.json). Apply
the same fix to the other read/parse block mentioned (the block around lines
405-410) so parse failures never overwrite existing settings.

Comment thread src/index.tsx Outdated
@LeonFedotov
Copy link
Copy Markdown
Author

Note: native bytecode builds

The --apply command works correctly but many patches (not just this PR's) fail on native binary installs due to Bun bytecode extraction — see #652 (comment) for details. Working on a fix for the extraction layer as a separate PR.

…lags

- Reject --install-hook + --remove-hook used together
- Distinguish missing file from JSON parse error
- Validate hooks/SessionStart are arrays before accessing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/index.tsx`:
- Around line 371-392: The code currently treats any fs read error as "file
missing" and accepts any JSON (including null/array), so update the settings
loading around settingsPath and settings: when fsSync.readFileSync throws,
inspect the error.code and for ENOENT allow fresh start but for EACCES/EPERM and
other errors log a descriptive error via console.error (include error.message)
and exit; when parsing JSON, validate that JSON.parse(raw) returns a non-null
object that is not an array before assigning to settings—if the parsed value is
null, not an object, or an array, log an error describing that settings.json
must be an object and exit—then compute hooks from the validated settings object
(use the existing hooks detection logic but ensure settings is an object).
- Around line 401-458: The current search uses includes(hookMarker) and treats a
whole SessionStart entry as managed, which can misidentify unrelated hooks and
lead to deleting unrelated hooks; update the logic that inspects
hooks.SessionStart to find the specific hook by exact command match (compare
hook.command === hookCommand or, if you prefer, a stronger marker check combined
with the same matcher) and capture both the sessionStart index and the specific
hook index; when installing, only skip if that exact hook already exists in any
sessionStart entry; when removing, remove only that specific hook from the found
sessionStart entry (sessionStart[foundIdx].hooks.splice(hookIdx, 1)) and only
delete hooks.SessionStart if its hooks array becomes empty; update references
around hookMarker/hookCommand, sessionStart, existingIdx, and where you
currently splice the entire sessionStart to implement this safer per-hook
add/remove behavior.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6ff7dc64-b4d1-447e-9a15-edb0ebe671e8

📥 Commits

Reviewing files that changed from the base of the PR and between 44cfb05 and 0534878.

📒 Files selected for processing (1)
  • src/index.tsx

Comment thread src/index.tsx
Comment thread src/index.tsx
@LeonFedotov
Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/index.tsx`:
- Around line 394-396: The catch block swallowing all errors after the
fs.readFileSync call must distinguish "file not found" from other errors; update
the catch to inspect the thrown error (e.g., NodeJS.ErrnoException.code) and
only treat ENOENT as “start fresh”, while rethrowing or surfacing any other
codes (EACCES/EPERM/etc.) so the process does not silently overwrite unreadable
files; locate the try/catch around readFileSync in src/index.tsx and modify that
catch accordingly.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8b94c5a3-a085-488b-ba07-743ab14144fa

📥 Commits

Reviewing files that changed from the base of the PR and between 0534878 and b50cca8.

📒 Files selected for processing (1)
  • src/index.tsx

Comment thread src/index.tsx Outdated
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Distinguish ENOENT (start fresh), SyntaxError (invalid JSON),
non-object JSON, and permission errors (EACCES/EPERM) as separate
cases instead of nested try/catch blocks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/index.tsx (1)

402-420: ⚠️ Potential issue | 🟠 Major

Only match well-formed managed hook entries.

Array.isArray(rawSessionStart) only validates the outer container. null items or entries with non-array hooks will still blow up in entry.hooks?.some(...) / findIndex(...), and the current predicate also treats the same command under a non-empty matcher as the managed hook. That means --install-hook can report "already installed" against a user-owned entry and --remove-hook can delete it later. Please guard each entry before calling array methods and require matcher === '', type === 'command', and the exact command when locating the managed hook.

Also applies to: 466-476

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/index.tsx` around lines 402 - 420, The current lookup for the managed
hook (using rawSessionStart/sessionStart and existingIdx) doesn't validate each
entry or hook shape; update the logic that derives sessionStart and the
findIndex predicate to only consider well-formed managed hooks by: ensuring each
sessionStart entry is an object (not null), has matcher === '' (exact empty
string), has a hooks property that's an array, and within that array only treat
hooks with type === 'command' and command === hookCommand as matches; adjust the
existingIdx computation (and the analogous code around lines 466-476) to both
guard against null/non-array values before calling .some/.findIndex and to
require the exact matcher/type/command constraints when locating the managed
hook.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/index.tsx`:
- Around line 443-448: Wrap the synchronous directory creation and file write
operations into a single guarded helper (e.g., ensureDirAndWriteSync or
safeWriteSettings) that takes settingsPath and contents, calls
fsSync.mkdirSync(path.dirname(settingsPath), { recursive: true }) and
fsSync.writeFileSync(settingsPath, ...), and catches any exceptions
(EACCES/EPERM/ENOSPC/etc.); on error, call the CLI logger/console.error with a
clear message including the path and error.message and exit with non-zero status
instead of letting the exception bubble. Replace the two existing write sites
that use fsSync.mkdirSync/fsSync.writeFileSync (the block around settingsPath
and the similar block at the other location) to call this helper so both
--install-hook / --remove-hook paths use the guarded writer.
- Around line 236-247: Reject combined hook and other top-level modes: instead
of short-circuiting, check that when options.installHook or options.removeHook
is set no other top-level action flags (e.g. options.apply, options.patches,
options.listPatches or any other mutually exclusive CLI mode flags used
elsewhere) are also present; if they are, print an error (similar to the
existing message) and exit non‑zero. Only call await
handleHookMode(!!options.installHook) and return when install/removeHook is the
sole top-level mode; otherwise fail fast. Use the existing symbols
options.installHook, options.removeHook and handleHookMode to locate and
implement the guard.

---

Duplicate comments:
In `@src/index.tsx`:
- Around line 402-420: The current lookup for the managed hook (using
rawSessionStart/sessionStart and existingIdx) doesn't validate each entry or
hook shape; update the logic that derives sessionStart and the findIndex
predicate to only consider well-formed managed hooks by: ensuring each
sessionStart entry is an object (not null), has matcher === '' (exact empty
string), has a hooks property that's an array, and within that array only treat
hooks with type === 'command' and command === hookCommand as matches; adjust the
existingIdx computation (and the analogous code around lines 466-476) to both
guard against null/non-array values before calling .some/.findIndex and to
require the exact matcher/type/command constraints when locating the managed
hook.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bdf12e64-85c5-4b67-be87-1af7ac816959

📥 Commits

Reviewing files that changed from the base of the PR and between 9ebb8d8 and 5b11d41.

📒 Files selected for processing (1)
  • src/index.tsx

Comment thread src/index.tsx
Comment on lines +236 to +247
// Handle --install-hook / --remove-hook flags
if (options.installHook && options.removeHook) {
console.error(
chalk.red(
'Error: Cannot use --install-hook and --remove-hook together.'
)
);
process.exit(1);
}
if (options.installHook || options.removeHook) {
await handleHookMode(!!options.installHook);
return;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reject mixed command modes instead of silently picking one.

This branch short-circuits the rest of the default action, so --remove-hook --apply / --install-hook --patches ... return success after only managing the hook, while --install-hook --list-patches never reaches this block because list mode returned earlier. Please make hook management mutually exclusive with the other top-level modes instead of ignoring part of the CLI input.

💡 Proposed guard
       if (options.installHook && options.removeHook) {
         console.error(
           chalk.red(
             'Error: Cannot use --install-hook and --remove-hook together.'
           )
         );
         process.exit(1);
       }
+      if (
+        (options.installHook || options.removeHook) &&
+        (options.apply ||
+          options.restore ||
+          options.revert ||
+          options.listPatches ||
+          options.listSystemPrompts !== undefined ||
+          options.configUrl ||
+          options.patches)
+      ) {
+        console.error(
+          chalk.red(
+            'Error: --install-hook/--remove-hook cannot be combined with other command modes.'
+          )
+        );
+        process.exit(1);
+      }
       if (options.installHook || options.removeHook) {
         await handleHookMode(!!options.installHook);
         return;
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/index.tsx` around lines 236 - 247, Reject combined hook and other
top-level modes: instead of short-circuiting, check that when
options.installHook or options.removeHook is set no other top-level action flags
(e.g. options.apply, options.patches, options.listPatches or any other mutually
exclusive CLI mode flags used elsewhere) are also present; if they are, print an
error (similar to the existing message) and exit non‑zero. Only call await
handleHookMode(!!options.installHook) and return when install/removeHook is the
sole top-level mode; otherwise fail fast. Use the existing symbols
options.installHook, options.removeHook and handleHookMode to locate and
implement the guard.

Comment thread src/index.tsx
Comment on lines +443 to +448
fsSync.mkdirSync(path.dirname(settingsPath), { recursive: true });
fsSync.writeFileSync(
settingsPath,
JSON.stringify(settings, null, 2) + '\n',
'utf8'
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle write-side failures before exiting.

mkdirSync / writeFileSync can still fail on EACCES, EPERM, disk-full, or a read-only file. Right now that turns --install-hook / --remove-hook into an uncaught exception path. Please funnel both write sites through one guarded helper so the CLI prints a clear write error instead of a stack trace.

💡 Proposed helper
+function writeClaudeSettings(
+  settingsPath: string,
+  settings: Record<string, unknown>
+): void {
+  try {
+    fsSync.mkdirSync(path.dirname(settingsPath), { recursive: true });
+    fsSync.writeFileSync(
+      settingsPath,
+      JSON.stringify(settings, null, 2) + '\n',
+      'utf8'
+    );
+  } catch (error) {
+    console.error(
+      chalk.red(
+        `Error writing ${settingsPath}: ${error instanceof Error ? error.message : String(error)}`
+      )
+    );
+    process.exit(1);
+  }
+}
+
 ...
-    fsSync.mkdirSync(path.dirname(settingsPath), { recursive: true });
-    fsSync.writeFileSync(
-      settingsPath,
-      JSON.stringify(settings, null, 2) + '\n',
-      'utf8'
-    );
+    writeClaudeSettings(settingsPath, settings);
 ...
-    fsSync.writeFileSync(
-      settingsPath,
-      JSON.stringify(settings, null, 2) + '\n',
-      'utf8'
-    );
+    writeClaudeSettings(settingsPath, settings);

Also applies to: 489-492

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/index.tsx` around lines 443 - 448, Wrap the synchronous directory
creation and file write operations into a single guarded helper (e.g.,
ensureDirAndWriteSync or safeWriteSettings) that takes settingsPath and
contents, calls fsSync.mkdirSync(path.dirname(settingsPath), { recursive: true
}) and fsSync.writeFileSync(settingsPath, ...), and catches any exceptions
(EACCES/EPERM/ENOSPC/etc.); on error, call the CLI logger/console.error with a
clear message including the path and error.message and exit with non-zero status
instead of letting the exception bubble. Replace the two existing write sites
that use fsSync.mkdirSync/fsSync.writeFileSync (the block around settingsPath
and the similar block at the other location) to call this helper so both
--install-hook / --remove-hook paths use the guarded writer.

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