diff --git a/server/__tests__/lane-detect.test.js b/server/__tests__/lane-detect.test.js index abed8c0..9a167f3 100644 --- a/server/__tests__/lane-detect.test.js +++ b/server/__tests__/lane-detect.test.js @@ -325,4 +325,19 @@ describe("checkProfile", () => { const result = await detect.checkProfile(repo); assert.ok(result.warnings.some((w) => /secrets\.env/.test(w))); }); + + it("fails when a hook file becomes unreadable (permission or race condition)", async () => { + const repo = makeRepo(); + writePkg(repo, { start: "node index.js" }); + detect.scaffoldProfile(repo, detect.detectNode(repo)); + const hookPath = path.join(repo, ".ccam", "profile", "hooks", "health.sh"); + fs.chmodSync(hookPath, 0o000); + try { + const result = await detect.checkProfile(repo); + assert.equal(result.ok, false); + assert.ok(result.errors.some((e) => /not readable/.test(e))); + } finally { + fs.chmodSync(hookPath, 0o755); + } + }); }); diff --git a/server/lib/lane-detect.js b/server/lib/lane-detect.js index 07fc35b..4d8bf0c 100644 --- a/server/lib/lane-detect.js +++ b/server/lib/lane-detect.js @@ -360,7 +360,13 @@ async function checkProfile(dir) { continue; } if (!(fs.statSync(hookPath).mode & 0o111)) errors.push(`hook not executable: ${name}.sh`); - const body = fs.readFileSync(hookPath, "utf8"); + let body; + try { + body = fs.readFileSync(hookPath, "utf8"); + } catch (err) { + errors.push(`${name}.sh is not readable`); + continue; + } if (containsTodo(body)) errors.push(`${name}.sh still has an unresolved TODO`); }