From a5c4a355b4ea203e57937ec64fc8619afa20ace4 Mon Sep 17 00:00:00 2001 From: mptyl Date: Wed, 12 Aug 2026 01:29:41 +0200 Subject: [PATCH] fix author git publication recovery --- backend/src/workspaces/git-repository.ts | 17 ++++++++++++++--- backend/test/workspace-registry.test.ts | 4 +--- backend/test/workspace-runtime-handoff.test.ts | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/backend/src/workspaces/git-repository.ts b/backend/src/workspaces/git-repository.ts index 07571ca9..065a77e8 100644 --- a/backend/src/workspaces/git-repository.ts +++ b/backend/src/workspaces/git-repository.ts @@ -232,6 +232,10 @@ export class GitWorkspaceRepository { try { await this.git(["push", "origin", `${revision}:refs/heads/${this.config.branch}`]); } catch (error) { + // `git` already returns a sanitized WorkspaceRegistryError. Preserve it rather + // than mapping its public code a second time (notably git_push_rejected). + await this.restoreFailedPublication(); + if (error instanceof WorkspaceRegistryError) throw error; throw this.sanitizeGitError(error); } } @@ -239,9 +243,16 @@ export class GitWorkspaceRepository { /** Create a local publication commit without contacting the remote. */ async commitOnly(paths: readonly string[], message: string): Promise { if (paths.length === 0 || paths.some((path) => !this.isRegistryArtifactPath(path))) throw new WorkspaceRegistryError("workspace_invalid", "Workspace repository path is invalid"); - await this.git(["add", "--", ...paths]); - await this.git(["commit", "-m", message], this.publicationIdentity()); - return await this.status(); + try { + await this.git(["add", "--", ...paths]); + await this.git(["commit", "-m", message], this.publicationIdentity()); + return await this.status(); + } catch (error) { + // A failed prepare must not leave staged or generated artifacts in the + // long-lived author checkout for the next request. + await this.restoreFailedPublication(); + throw error; + } } /** Commit and push a fixed set of validated artifact paths without exposing Git output. */ diff --git a/backend/test/workspace-registry.test.ts b/backend/test/workspace-registry.test.ts index 025b7d3e..e6dbdeab 100644 --- a/backend/test/workspace-registry.test.ts +++ b/backend/test/workspace-registry.test.ts @@ -1142,9 +1142,7 @@ test("rejects a symbolic-link registry root before creating a lock below it", as const root = join(remote.root, "registry-link"); mkdirSync(target); symlinkSync(target, root); - const registry = makeRegistry(config(root, remote.remote)); - - await expect(bootstrap(registry)).rejects.toMatchObject({ code: "git_unavailable" }); + expect(() => makeRegistry(config(root, remote.remote))).toThrow("invalid sessions root"); expect(existsSync(join(target, "locks"))).toBe(false); }); diff --git a/backend/test/workspace-runtime-handoff.test.ts b/backend/test/workspace-runtime-handoff.test.ts index bd22c179..a4489f37 100644 --- a/backend/test/workspace-runtime-handoff.test.ts +++ b/backend/test/workspace-runtime-handoff.test.ts @@ -83,7 +83,7 @@ async function git(cwd: string, args: string[]): Promise { } async function fixture(workspaceSource = filesystemWorkspace) { - const root = mkdtempSync(join(tmpdir(), "tht-runtime-handoff-")); + const root = realpathSync(mkdtempSync(join(tmpdir(), "tht-runtime-handoff-"))); roots.push(root); const remote = join(root, "remote.git"); const source = join(root, "source");