Skip to content

Commit b68ec7f

Browse files
IAmJSDclaude
andcommitted
test(vcs): cover the local permission phrasings git actually writes
Bugbot named `.git/index.lock` and `FETCH_HEAD` specifically. Both already classify as command-failed, but only the clone-target phrasing was covered, so nothing pinned the other two. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
1 parent e721f82 commit b68ec7f

1 file changed

Lines changed: 46 additions & 27 deletions

File tree

‎apps/server/src/vcs/GitVcsDriverCore.test.ts‎

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -722,34 +722,53 @@ it.effect("classifies an authentication failure without retaining the remote it
722722
).pipe(Effect.provide(failing.layer));
723723
});
724724

725-
it.effect("does not read a local permission error as an authentication failure", () => {
726-
// `git init` into an unwritable directory says "Permission denied" too.
727-
// Matching that bare phrase would answer a filesystem problem with advice
728-
// about remote credentials, which is worse than saying nothing.
729-
const failing = makeFailingGitLayer(
730-
`error: could not create work tree dir 'projects': Permission denied\n`,
731-
);
732-
733-
return Effect.scoped(
734-
Effect.gen(function* () {
735-
const driver = yield* GitVcsDriver.GitVcsDriver;
736-
const cwd = yield* makeTmpDir();
737-
const error = yield* driver
738-
.execute({
739-
operation: "GitVcsDriver.test.localPermissionFailure",
740-
cwd,
741-
args: ["init", "projects"],
742-
})
743-
.pipe(Effect.flip);
725+
// Git writes a bare "Permission denied" for local filesystem problems, using
726+
// the same two words ssh uses for a rejected key. Matching the phrase alone
727+
// would answer an unwritable directory or a stale lock with advice about
728+
// remote credentials, which is worse than saying nothing.
729+
const localPermissionFailures = [
730+
{
731+
name: "an unwritable clone target",
732+
args: ["init", "projects"],
733+
stderr: `error: could not create work tree dir 'projects': Permission denied\n`,
734+
},
735+
{
736+
name: "a lock file it cannot take",
737+
args: ["commit", "-m", "wip"],
738+
stderr: `fatal: Unable to create '/repo/.git/index.lock': Permission denied\n`,
739+
},
740+
{
741+
name: "a ref file it cannot write",
742+
args: ["fetch", "origin"],
743+
stderr: `error: cannot open .git/FETCH_HEAD: Permission denied\n`,
744+
},
745+
] as const;
746+
747+
for (const failure of localPermissionFailures) {
748+
it.effect(`reads ${failure.name} as a command failure, not an auth failure`, () => {
749+
const failing = makeFailingGitLayer(failure.stderr);
750+
751+
return Effect.scoped(
752+
Effect.gen(function* () {
753+
const driver = yield* GitVcsDriver.GitVcsDriver;
754+
const cwd = yield* makeTmpDir();
755+
const error = yield* driver
756+
.execute({
757+
operation: "GitVcsDriver.test.localPermissionFailure",
758+
cwd,
759+
args: [...failure.args],
760+
})
761+
.pipe(Effect.flip);
744762

745-
assert.equal(failing.spawns.length, 1);
746-
assert.instanceOf(error, GitCommandError);
747-
assert.equal(error.failureKind, "command-failed");
748-
assert.notInclude(error.detail.toLowerCase(), "authentication");
749-
assert.notInclude(error.detail.toLowerCase(), "credentials");
750-
}),
751-
).pipe(Effect.provide(failing.layer));
752-
});
763+
assert.equal(failing.spawns.length, 1);
764+
assert.instanceOf(error, GitCommandError);
765+
assert.equal(error.failureKind, "command-failed");
766+
assert.notInclude(error.detail.toLowerCase(), "authentication");
767+
assert.notInclude(error.detail.toLowerCase(), "credentials");
768+
}),
769+
).pipe(Effect.provide(failing.layer));
770+
});
771+
}
753772

754773
it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
755774
describe("process environment", () => {

0 commit comments

Comments
 (0)