From b34f8e03ed7a18ac693da295e7604fb610ae574e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Sun, 17 May 2026 19:09:23 +0200 Subject: [PATCH] fix(router): skip git fetch/checkout when /workspace has uncommitted changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Session pods crashloop in Init:0/1 when the PVC at /workspace carries uncommitted edits from a prior session — `git checkout` aborts with "local changes would be overwritten" and the user is locked out of their session. Detect a dirty workspace (any tracked diff or non-ignored untracked file) and skip the git fetch + checkout phase entirely when present. The workspace was initialized on an earlier pod start so the pod can come up against the existing state. To get back onto the session branch the user commits or discards the changes from inside the pod and restarts. This preserves the user's mental model ("my work is where I left it") with no surprising stash entries piling up and no risk of pop-time conflicts. A fresh-clone workspace is never dirty so the first-start path is unchanged. Co-Authored-By: Claude Opus 4.7 --- .../opencode-router/src/pod-manager.test.ts | 31 +++++++++++++++++++ packages/opencode-router/src/pod-manager.ts | 21 ++++++++++--- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/packages/opencode-router/src/pod-manager.test.ts b/packages/opencode-router/src/pod-manager.test.ts index 05b93af39f39..b5b23532bdfd 100644 --- a/packages/opencode-router/src/pod-manager.test.ts +++ b/packages/opencode-router/src/pod-manager.test.ts @@ -333,6 +333,37 @@ describe("listUserSessions", () => { // Must NOT try to look up "calm-snails-dream" on remote (it's always a new branch) expect(script).not.toContain('ls-remote --exit-code --heads origin "calm-snails-dream"') }) + + it("ensurePod git-init script skips fetch/checkout when /workspace has uncommitted changes from a prior session", async () => { + // Pod restarts re-mount the PVC which can contain uncommitted edits from the previous + // session. Running `git checkout` against a dirty workspace aborts with "local changes + // would be overwritten" and the init container crashloops. The script must detect the + // dirty state and skip the git phase, leaving the workspace exactly as the user left it. + fakePVCs = [] + fakePods = [] + createPodCalls = [] + + const { ensurePod } = await import("./pod-manager.js") + const session = { email: EMAIL, repoUrl: REPO, branch: "resilient-branch", sourceBranch: "main" } + + await (ensurePod as any)(computeHash(EMAIL, REPO, "resilient-branch"), session) + + expect(createPodCalls).toHaveLength(1) + const pod = (createPodCalls[0] as any).body + const script: string = pod.spec.initContainers[0].args[0] + // Dirty-workspace guard must check both tracked diffs and untracked files + expect(script).toContain("$GIT diff --quiet HEAD") + expect(script).toContain("$GIT ls-files --others --exclude-standard") + // The guard wraps fetch + both checkout branches so none of them run when dirty + const guardIdx = script.indexOf("$GIT diff --quiet HEAD") + const fetchIdx = script.indexOf("$GIT fetch --all") + const checkoutIdx = script.indexOf("$GIT checkout") + expect(guardIdx).toBeGreaterThan(-1) + expect(fetchIdx).toBeGreaterThan(guardIdx) + expect(checkoutIdx).toBeGreaterThan(guardIdx) + // Must NOT introduce an auto-stash — work is preserved by leaving the workspace untouched + expect(script).not.toContain("stash push") + }) }) // --------------------------------------------------------------------------- diff --git a/packages/opencode-router/src/pod-manager.ts b/packages/opencode-router/src/pod-manager.ts index 17ed6fd2d8ad..b3966d6897d2 100644 --- a/packages/opencode-router/src/pod-manager.ts +++ b/packages/opencode-router/src/pod-manager.ts @@ -658,12 +658,23 @@ export async function ensurePod( ` git clone "${repoUrl}" /workspace`, `fi`, `cd /workspace`, - `$GIT fetch --all`, - `if $GIT rev-parse --verify "${branch}" >/dev/null 2>&1; then`, - ` $GIT checkout "${branch}"`, + // If the workspace carries uncommitted work from a prior session, skip + // fetch + checkout entirely and start the pod against the existing + // state. The repo was initialized on an earlier pod start so the user + // can keep working; otherwise `git checkout` would abort with "local + // changes would be overwritten" and the pod crashloops in Init:0/1. + // To get back on the session branch, commit or discard the changes + // from inside the pod and restart. + `if ! $GIT diff --quiet HEAD 2>/dev/null || [ -n "$($GIT ls-files --others --exclude-standard 2>/dev/null)" ]; then`, + ` echo "opencode-init: uncommitted changes detected in /workspace; skipping git fetch/checkout to preserve work"`, `else`, - ` $GIT checkout -B "${sourceBranch}" "origin/${sourceBranch}"`, - ` $GIT checkout -b "${branch}"`, + ` $GIT fetch --all`, + ` if $GIT rev-parse --verify "${branch}" >/dev/null 2>&1; then`, + ` $GIT checkout "${branch}"`, + ` else`, + ` $GIT checkout -B "${sourceBranch}" "origin/${sourceBranch}"`, + ` $GIT checkout -b "${branch}"`, + ` fi`, `fi`, ] : [