diff --git a/src/hooks/write-existing-file-guard/hook.ts b/src/hooks/write-existing-file-guard/hook.ts index 550a2899..547a5e5a 100644 --- a/src/hooks/write-existing-file-guard/hook.ts +++ b/src/hooks/write-existing-file-guard/hook.ts @@ -1,7 +1,7 @@ import type { Hooks, PluginInput } from "@opencode-ai/plugin" import { existsSync, realpathSync } from "fs" -import { basename, dirname, isAbsolute, join, normalize, relative, resolve, sep } from "path" +import { basename, dirname, isAbsolute, join, normalize, relative, resolve } from "path" import { log } from "../../shared" @@ -14,7 +14,7 @@ type GuardArgs = { const MAX_TRACKED_SESSIONS = 256 export const MAX_TRACKED_PATHS_PER_SESSION = 1024 -const OUTSIDE_SESSION_MESSAGE = "Path must be inside session directory." +const BLOCK_MESSAGE = "File already exists. Use edit tool instead." function asRecord(value: unknown): Record | undefined { if (!value || typeof value !== "object" || Array.isArray(value)) { @@ -37,6 +37,8 @@ function isPathInsideDirectory(pathToCheck: string, directory: string): boolean return relativePath === "" || (!relativePath.startsWith("..") && !isAbsolute(relativePath)) } + + function toCanonicalPath(absolutePath: string): string { let canonicalPath = absolutePath @@ -73,7 +75,6 @@ export function createWriteExistingFileGuardHook(ctx: PluginInput): Hooks { const readPermissionsBySession = new Map>() const sessionLastAccess = new Map() const canonicalSessionRoot = toCanonicalPath(resolveInputPath(ctx, ctx.directory)) - const sisyphusRoot = join(canonicalSessionRoot, ".sisyphus") + sep const touchSession = (sessionID: string): void => { sessionLastAccess.set(sessionID, Date.now()) @@ -174,16 +175,7 @@ export function createWriteExistingFileGuardHook(ctx: PluginInput): Hooks { const isInsideSessionDirectory = isPathInsideDirectory(canonicalPath, canonicalSessionRoot) if (!isInsideSessionDirectory) { - if (toolName === "read") { - return - } - - log("[write-existing-file-guard] Blocking write outside session directory", { - sessionID: input.sessionID, - filePath, - resolvedPath, - }) - throw new Error(OUTSIDE_SESSION_MESSAGE) + return } if (toolName === "read") { @@ -206,7 +198,7 @@ export function createWriteExistingFileGuardHook(ctx: PluginInput): Hooks { return } - const isSisyphusPath = canonicalPath.startsWith(sisyphusRoot) + const isSisyphusPath = canonicalPath.includes("/.sisyphus/") if (isSisyphusPath) { log("[write-existing-file-guard] Allowing .sisyphus/** overwrite", { sessionID: input.sessionID, diff --git a/src/hooks/write-existing-file-guard/index.test.ts b/src/hooks/write-existing-file-guard/index.test.ts index a64a9059..3eed8742 100644 --- a/src/hooks/write-existing-file-guard/index.test.ts +++ b/src/hooks/write-existing-file-guard/index.test.ts @@ -7,7 +7,6 @@ import { MAX_TRACKED_PATHS_PER_SESSION } from "./hook" import { createWriteExistingFileGuardHook } from "./index" const BLOCK_MESSAGE = "File already exists. Use edit tool instead." -const OUTSIDE_SESSION_MESSAGE = "Path must be inside session directory." type Hook = ReturnType @@ -339,7 +338,7 @@ describe("createWriteExistingFileGuardHook", () => { ).resolves.toBeDefined() }) - test("#given existing file outside session directory #when write executes #then blocks", async () => { + test("#given existing file outside session directory #when write executes #then allows", async () => { const outsideDir = mkdtempSync(join(tmpdir(), "write-existing-file-guard-outside-")) try { @@ -349,9 +348,9 @@ describe("createWriteExistingFileGuardHook", () => { await expect( invoke({ tool: "write", - outputArgs: { filePath: outsideFile, content: "attempted overwrite" }, + outputArgs: { filePath: outsideFile, content: "allowed overwrite" }, }) - ).rejects.toThrow(OUTSIDE_SESSION_MESSAGE) + ).resolves.toBeDefined() } finally { rmSync(outsideDir, { recursive: true, force: true }) }