Skip to content

feat: guard write tool from overwriting existing files#1476

Merged
code-yeongyu merged 1 commit intodevfrom
feat/write-existing-file-guard
Feb 4, 2026
Merged

feat: guard write tool from overwriting existing files#1476
code-yeongyu merged 1 commit intodevfrom
feat/write-existing-file-guard

Conversation

@code-yeongyu
Copy link
Owner

@code-yeongyu code-yeongyu commented Feb 4, 2026

Summary

  • Adds a PreToolUse hook that intercepts write operations and throws an error if the target file already exists
  • Error message: "File already exists. Use edit tool instead."
  • Hook is enabled by default, can be disabled via disabled_hooks: ["write-existing-file-guard"]

Changes

  • Created src/hooks/write-existing-file-guard/index.ts - hook implementation
  • Created src/hooks/write-existing-file-guard/index.test.ts - comprehensive test suite with BDD-style comments
  • Added "write-existing-file-guard" to HookNameSchema in src/config/schema.ts
  • Exported hook from src/hooks/index.ts
  • Registered hook in src/index.ts tool.execute.before chain

Test Plan

  • 5 test cases covering:
    • Allows write to non-existing file
    • Blocks write to existing file
    • Case-insensitive tool name matching
    • Ignores non-write tools
    • Handles missing filePath arg gracefully
  • Typecheck passes
  • Build passes

Summary by cubic

Adds a pre-execution hook that blocks the Write tool from overwriting existing files. Enabled by default to prevent accidental data loss; prompts using the Edit tool instead.

  • New Features
    • Blocks Write when the target file exists; throws "File already exists. Use edit tool instead."
    • Supports filePath, path, and file_path; resolves relative paths using ctx.directory.
    • Case-insensitive tool match; ignores non-write tools; no-op when path arg is missing.
    • Toggle via disabled_hooks: ["write-existing-file-guard"]; registered in tool.execute.before, exported, and added to schema; tests cover common scenarios.

Written for commit ddf878e. Summary will update on new commits.

@code-yeongyu
Copy link
Owner Author

Related to #1474 - Thank you @kaizen403 for the inspiration! Your work on protecting plan files from overwrites motivated this more general hook that guards all existing files when using the Write tool.

Once both PRs are merged, we'll have comprehensive protection:

@code-yeongyu
Copy link
Owner Author

This PR provides a more fundamental solution to the problem addressed in #1474.

While #1474 adds plan-specific protection in the prometheus-md-only hook, this PR implements a general-purpose hook that guards all existing files from being overwritten by the Write tool - addressing the root cause at the tool level.

Once this is merged, #1474 can be closed as this provides broader coverage.

@kaizen403 Thank you so much for identifying this issue and your contribution! Your work helped highlight the need for this protection. 🙏

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 7 files

Confidence score: 3/5

  • There is a concrete bypass risk: src/hooks/write-existing-file-guard/index.ts only checks filePath, while other tools use path/file_path, so the guard may not trigger and existing files could be overwritten.
  • Relative path handling in src/hooks/write-existing-file-guard/index.test.ts relies on fs.existsSync(filePath) with process.cwd(), which can diverge from ctx.directory and allow incorrect existence checks.
  • Score reflects medium severity, user-impacting guard failures but no evidence of broader breakage.
  • Pay close attention to src/hooks/write-existing-file-guard/index.ts, src/hooks/write-existing-file-guard/index.test.ts - guard may be bypassed due to argument naming and CWD mismatch.
Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/hooks/write-existing-file-guard/index.test.ts">

<violation number="1" location="src/hooks/write-existing-file-guard/index.test.ts:8">
P1: The hook implementation relies on `fs.existsSync(filePath)` which resolves relative paths against `process.cwd()`. This is unsafe as the agent's working directory (`ctx.directory`) often differs from the process CWD (e.g. in VSCode workspaces or monorepos).

The hook factory should be updated to accept `ctx` and use `path.resolve(ctx.directory, filePath)`.

This test instantiation should then be updated to pass a mock context with `tempDir`.</violation>
</file>

<file name="src/hooks/write-existing-file-guard/index.ts">

<violation number="1" location="src/hooks/write-existing-file-guard/index.ts:13">
P2: The hook only checks for `filePath`, but the `write` tool (and others) often use `path` or `file_path` as the argument name (see `src/hooks/comment-checker/index.ts`). This could cause the guard to be bypassed if the tool uses one of those alternatives.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

import * as os from "os"

describe("createWriteExistingFileGuardHook", () => {
const hook = createWriteExistingFileGuardHook()
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 4, 2026

Choose a reason for hiding this comment

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

P1: The hook implementation relies on fs.existsSync(filePath) which resolves relative paths against process.cwd(). This is unsafe as the agent's working directory (ctx.directory) often differs from the process CWD (e.g. in VSCode workspaces or monorepos).

The hook factory should be updated to accept ctx and use path.resolve(ctx.directory, filePath).

This test instantiation should then be updated to pass a mock context with tempDir.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/hooks/write-existing-file-guard/index.test.ts, line 8:

<comment>The hook implementation relies on `fs.existsSync(filePath)` which resolves relative paths against `process.cwd()`. This is unsafe as the agent's working directory (`ctx.directory`) often differs from the process CWD (e.g. in VSCode workspaces or monorepos).

The hook factory should be updated to accept `ctx` and use `path.resolve(ctx.directory, filePath)`.

This test instantiation should then be updated to pass a mock context with `tempDir`.</comment>

<file context>
@@ -0,0 +1,87 @@
+import * as os from "os"
+
+describe("createWriteExistingFileGuardHook", () => {
+  const hook = createWriteExistingFileGuardHook()
+  let tempDir: string
+
</file context>
Fix with Cubic

return
}

const args = output.args as { filePath?: string } | undefined
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 4, 2026

Choose a reason for hiding this comment

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

P2: The hook only checks for filePath, but the write tool (and others) often use path or file_path as the argument name (see src/hooks/comment-checker/index.ts). This could cause the guard to be bypassed if the tool uses one of those alternatives.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/hooks/write-existing-file-guard/index.ts, line 13:

<comment>The hook only checks for `filePath`, but the `write` tool (and others) often use `path` or `file_path` as the argument name (see `src/hooks/comment-checker/index.ts`). This could cause the guard to be bypassed if the tool uses one of those alternatives.</comment>

<file context>
@@ -0,0 +1,29 @@
+        return
+      }
+
+      const args = output.args as { filePath?: string } | undefined
+      const filePath = args?.filePath
+      if (!filePath) {
</file context>
Fix with Cubic

@kaizen403
Copy link
Contributor

@code-yeongyu glad I could contribute :) this general approach is much better!

@code-yeongyu code-yeongyu force-pushed the feat/write-existing-file-guard branch from 28ef939 to 601ee9d Compare February 4, 2026 07:45
…overwriting existing files

Adds a PreToolUse hook that intercepts write operations and throws an error
if the target file already exists, guiding users to use the edit tool instead.

- Throws error: 'File already exists. Use edit tool instead.'
- Hook is enabled by default, can be disabled via disabled_hooks
- Includes comprehensive test suite with BDD-style comments
@code-yeongyu code-yeongyu force-pushed the feat/write-existing-file-guard branch from 601ee9d to ddf878e Compare February 4, 2026 16:59
@code-yeongyu
Copy link
Owner Author

@cubic-dev-ai please re-review - I've addressed both issues:

  1. Now checks filePath, path, and file_path argument names
  2. Uses ctx.directory to resolve relative paths instead of process.cwd()

@cubic-dev-ai
Copy link

cubic-dev-ai bot commented Feb 4, 2026

@cubic-dev-ai please re-review - I've addressed both issues:

  1. Now checks filePath, path, and file_path argument names
  2. Uses ctx.directory to resolve relative paths instead of process.cwd()

@code-yeongyu I have started the AI code review. It will take a few minutes to complete.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 5 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

@code-yeongyu code-yeongyu merged commit 0fbf863 into dev Feb 4, 2026
9 checks passed
@code-yeongyu code-yeongyu deleted the feat/write-existing-file-guard branch February 4, 2026 17:31
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.

2 participants