Trigger checkout fails across filesystems: git clone --local → EXDEV #57

Closed
opened 2026-10-22 17:16:29 +02:00 by omp-deck · 0 comments
Collaborator

Implementation: #58

Problem

Forgejo trigger 1104699d-7f89-4cd6-83c4-72116bf5da2f (a pull_request_comment on PR #56, 2026-10-22T14:01Z) failed before any session started. The webhook delivery was verified and accepted (HTTP 202). The checkout step failed:

could not prepare a checkout: Cloning into '/data/.config/omp-deck/checkouts/lucas/omp-deck/pr-56'...
fatal: failed to create link '.../pr-56/.git/objects/pack/pack-2043a92….pack': Invalid cross-device link

createCheckout in apps/server/src/forgejo/checkout.ts (line ~71) runs git clone --local --no-checkout <workspacePath> <dest>. The checkout root (<dataDir>/checkouts, here /data/.config/omp-deck/checkouts) is on a different filesystem than the workspace (/workspace/omp-deck), so hardlinking objects fails with EXDEV.

git's builtin/clone.c (copy_or_link_directory) tries link() first. If that fails it copies the file instead, unless --local was passed explicitly (option_local > 0), in which case it calls die_errno("failed to create link ..."). A local-path clone without --local still uses the local transport and hardlinks when it can. Dropping the flag fixes the cross-device case and keeps the cheap path when both are on the same filesystem.

Plan

Approach

  1. In apps/server/src/forgejo/checkout.ts, createCheckout: change the clone argv from ["clone", "--local", "--no-checkout", opts.workspacePath, dest] to ["clone", "--no-checkout", opts.workspacePath, dest]. Do not add --no-hardlinks, because hardlinks are still preferred when they work. Make no other changes to the function.
  2. In the same file, update the module doc comment (lines ~9–11). Replace the sentence "--local hardlinks objects (cheap, no network)" with: "Cloning from the local path hardlinks objects when the checkout root shares the workspace's filesystem and copies them otherwise (no network either way); an explicit --local would make a cross-device link fatal instead of falling back." Keep the following clause about never using --shared/--reference unchanged.
  3. Do not add a permanent test. Reproducing the bug needs two filesystems, which the test environment can't be relied on to provide. The existing apps/server/src/forgejo/checkout.test.ts covers same-filesystem behavior and must keep passing.

Verification

  • From /workspace/omp-deck: bun test apps/server/src/forgejo/checkout.test.ts passes.
  • End-to-end in the deployment where the checkout root and the workspace are on different filesystems: after deploying, post a new comment on PR #56. The new trigger (see Settings → Forgejo, or deck_forgejo_trigger_list for lucas/omp-deck #56) reaches a started session (non-null sessionId, no "could not prepare a checkout" reason), and /data/.config/omp-deck/checkouts/lucas/omp-deck/pr-56 exists with HEAD at the PR head.
Implementation: #58 ## Problem Forgejo trigger `1104699d-7f89-4cd6-83c4-72116bf5da2f` (a `pull_request_comment` on PR #56, 2026-10-22T14:01Z) failed before any session started. The webhook delivery was verified and accepted (HTTP 202). The checkout step failed: ``` could not prepare a checkout: Cloning into '/data/.config/omp-deck/checkouts/lucas/omp-deck/pr-56'... fatal: failed to create link '.../pr-56/.git/objects/pack/pack-2043a92….pack': Invalid cross-device link ``` `createCheckout` in `apps/server/src/forgejo/checkout.ts` (line ~71) runs `git clone --local --no-checkout <workspacePath> <dest>`. The checkout root (`<dataDir>/checkouts`, here `/data/.config/omp-deck/checkouts`) is on a different filesystem than the workspace (`/workspace/omp-deck`), so hardlinking objects fails with EXDEV. git's `builtin/clone.c` (`copy_or_link_directory`) tries `link()` first. If that fails it copies the file instead, unless `--local` was passed explicitly (`option_local > 0`), in which case it calls `die_errno("failed to create link ...")`. A local-path clone without `--local` still uses the local transport and hardlinks when it can. Dropping the flag fixes the cross-device case and keeps the cheap path when both are on the same filesystem. ## Plan ### Approach 1. In `apps/server/src/forgejo/checkout.ts`, `createCheckout`: change the clone argv from `["clone", "--local", "--no-checkout", opts.workspacePath, dest]` to `["clone", "--no-checkout", opts.workspacePath, dest]`. Do not add `--no-hardlinks`, because hardlinks are still preferred when they work. Make no other changes to the function. 2. In the same file, update the module doc comment (lines ~9–11). Replace the sentence "`--local` hardlinks objects (cheap, no network)" with: "Cloning from the local path hardlinks objects when the checkout root shares the workspace's filesystem and copies them otherwise (no network either way); an explicit `--local` would make a cross-device link fatal instead of falling back." Keep the following clause about never using `--shared`/`--reference` unchanged. 3. Do not add a permanent test. Reproducing the bug needs two filesystems, which the test environment can't be relied on to provide. The existing `apps/server/src/forgejo/checkout.test.ts` covers same-filesystem behavior and must keep passing. ### Verification - From `/workspace/omp-deck`: `bun test apps/server/src/forgejo/checkout.test.ts` passes. - End-to-end in the deployment where the checkout root and the workspace are on different filesystems: after deploying, post a new comment on PR #56. The new trigger (see Settings → Forgejo, or `deck_forgejo_trigger_list` for `lucas/omp-deck` #56) reaches a started session (non-null `sessionId`, no "could not prepare a checkout" reason), and `/data/.config/omp-deck/checkouts/lucas/omp-deck/pr-56` exists with HEAD at the PR head.
lucas closed this issue 2026-10-22 17:22:17 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
lucas/omp-deck#57
No description provided.