Skip to content

fix(plugins): stage git plugin clone on target filesystem to avoid EXDEV#99896

Merged
vincentkoc merged 2 commits into
openclaw:mainfrom
Bartok9:fix/99885-git-plugin-exdev
Jul 5, 2026
Merged

fix(plugins): stage git plugin clone on target filesystem to avoid EXDEV#99896
vincentkoc merged 2 commits into
openclaw:mainfrom
Bartok9:fix/99885-git-plugin-exdev

Conversation

@Bartok9

@Bartok9 Bartok9 commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Git-backed plugin installs cloned into os.tmpdir() and then published the
managed repository with an atomic rename into the OpenClaw state directory.
When those paths were on different filesystems, the rename failed with
EXDEV: cross-device link not permitted and the install aborted.

Fixes #99885.

Why This Change Was Made

Non-dry-run clones now use a private temporary workspace beside the exact
managed git-<hash>/repo target. The staged clone and the rename target
therefore share a filesystem, preserving atomic replacement without adding a
copy fallback to the shared filesystem primitive.

withTempDir accepts an optional root for this owner-controlled staging path.
Dry runs deliberately keep the default OS temp workspace so validation remains
ephemeral and does not create managed state. If the target parent cannot be
prepared, the existing structured managed-repository replacement error path is
preserved. Workspace initialization fallback happens only before the clone
callback starts, so clone and dependency-install side effects are never retried.

User Impact

openclaw plugins install git:<repo> and git-backed plugin updates now work
when the OpenClaw state directory is mounted separately from /tmp, including
common container and bind-mount layouts. Existing install policy, dependency
scan, URL redaction, and atomic replacement behavior are unchanged.

Evidence

  • Focused Testbox serial run: 208 tests passed across git install, install
    source, install directory, replace file, and CLI install.
  • Path-scoped Testbox pnpm check:changed passed core/core-test typecheck,
    lint, import cycles, and policy guards.
  • The test-project routing contract passes with the plugin test registered as a
    temp-helper importer (83 tooling tests).
  • Real CLI install passed with the source on device 64768 and OpenClaw state
    plus the managed clone on device 21.
  • Dry-run regression coverage proves the managed git root remains absent.
  • Fresh branch autoreview reported no accepted/actionable findings.

@openclaw-barnacle openclaw-barnacle Bot added size: M triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. labels Jul 4, 2026
@clawsweeper

clawsweeper Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 5, 2026, 12:34 AM ET / 04:34 UTC.

Summary
The PR adds an optional temp-root parameter to the internal temp helper, stages non-dry-run git plugin clones beside the managed repository target, and adds focused regression coverage plus test-project routing.

PR surface: Source +38, Tests +128. Total +166 across 4 files.

Reproducibility: yes. Current main stages git clones under os.tmpdir() and then uses a rename-only directory replacement into the managed git repo, which source-proves the EXDEV path when those roots are on different filesystems; I did not rerun a live Docker repro in this read-only review.

Review metrics: none identified.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #99885
Summary: This PR is the active candidate fix for the canonical git plugin install EXDEV issue; the alternate PR for the same root cause was already closed as superseded by this one.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Next step before merge

  • No ClawSweeper repair is needed; the remaining action is maintainer review and normal required-check gating for the current PR head.

Security
Cleared: The diff changes local git plugin staging paths and tests without adding dependencies, workflow permissions, secret handling, lockfile changes, or new external execution beyond the existing git/npm install flow.

Review details

Best possible solution:

Land the same-filesystem staging fix after current-head CI completes, preserving dry-run ephemerality and the existing atomic replacement primitive.

Do we have a high-confidence way to reproduce the issue?

Yes. Current main stages git clones under os.tmpdir() and then uses a rename-only directory replacement into the managed git repo, which source-proves the EXDEV path when those roots are on different filesystems; I did not rerun a live Docker repro in this read-only review.

Is this the best way to solve the issue?

Yes. Same-filesystem staging is the narrowest maintainable fix because it keeps the existing atomic replacement semantics instead of adding a broader copy fallback to the git publish path.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against e069cb26f72a.

Label changes

Label changes:

  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body and follow-up comments provide redacted terminal-style proof for distinct source/state devices with the old EXDEV failure and new same-filesystem success, and the current-head Real behavior proof check is green.
  • add rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body and follow-up comments provide redacted terminal-style proof for distinct source/state devices with the old EXDEV failure and new same-filesystem success, and the current-head Real behavior proof check is green.

Label justifications:

  • P2: The PR fixes a real plugin install failure in split-filesystem Docker/container layouts with a limited plugin-install blast radius.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body and follow-up comments provide redacted terminal-style proof for distinct source/state devices with the old EXDEV failure and new same-filesystem success, and the current-head Real behavior proof check is green.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body and follow-up comments provide redacted terminal-style proof for distinct source/state devices with the old EXDEV failure and new same-filesystem success, and the current-head Real behavior proof check is green.
Evidence reviewed

PR surface:

Source +38, Tests +128. Total +166 across 4 files.

View PR surface stats
Area Files Added Removed Net
Source 2 40 2 +38
Tests 2 128 0 +128
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 4 168 2 +166

What I checked:

  • Current main failure path: Current main still creates git plugin clone workspaces through withTempDir, whose default root is os.tmpdir(), then publishes the staged repo through replaceDirectoryAtomic into the managed git directory. (src/plugins/git-install.ts:338, e069cb26f72a)
  • Patch stages on target filesystem: The PR head introduces withGitStagingDir, creates the target parent, uses it as the temp root for non-dry-run installs, and leaves dry runs on the default temp path. (src/plugins/git-install.ts:245, 43ca9a0163f1)
  • Regression tests cover the changed invariant: The PR adds tests that verify the clone parent matches the managed repo parent, workspace creation can fall back before callback side effects, and dry-run staging does not create the managed git root. (src/plugins/git-install.test.ts:491, 43ca9a0163f1)
  • Dependency contract checked: @openclaw/fs-safe 0.4.1 temp workspaces create mkdtemp directories under the supplied rootDir, while replaceDirectoryAtomic performs guarded renames without an EXDEV copy fallback, so same-filesystem staging is the narrow fix. (src/infra/private-temp-workspace.ts:12, e069cb26f72a)
  • Related issue and alternate PR: The linked issue is still open and describes the same Docker/bind-mount EXDEV failure; the alternate copy-fallback PR was closed as superseded by this same-filesystem staging PR.
  • CI and proof context: Live PR data shows the current head is mergeable but unstable because CI is still running; current-head Real behavior proof checks are green and the PR body/comments describe split-device terminal proof. (43ca9a0163f1)

Likely related people:

  • steipete: Git blame and shortlog point to Peter Steinberger as the dominant contributor on the current git install and install-helper surfaces in this checkout. (role: introduced behavior / central area contributor; confidence: medium; commits: b3a74ccc3aef, 6aa80844b82e, d72734946ab4; files: src/plugins/git-install.ts, src/infra/install-source-utils.ts, src/infra/replace-file.ts)
  • vincentkoc: Current main history includes Vincent Koc on the release snapshot that carries these installer files, and the live PR is assigned to him with a current-head fixup commit. (role: recent area contributor / PR assignee; confidence: high; commits: e085fa1a3ffd, 43ca9a0163f1; files: src/plugins/git-install.ts, src/infra/install-source-utils.ts, src/infra/replace-file.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (9 earlier review cycles; latest 8 shown)
  • reviewed 2026-07-04T08:52:44.290Z sha b8e86a1 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-04T09:08:24.863Z sha b8e86a1 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-05T02:55:01.388Z sha aea559f :: needs maintainer review before merge. :: none
  • reviewed 2026-07-05T03:00:41.821Z sha aea559f :: needs maintainer review before merge. :: none
  • reviewed 2026-07-05T03:20:01.348Z sha 2b5f160 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-05T03:26:30.278Z sha 2b5f160 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-05T03:50:50.444Z sha f2cdc7a :: needs maintainer review before merge. :: none
  • reviewed 2026-07-05T03:56:36.765Z sha f2cdc7a :: needs maintainer review before merge. :: none

@openclaw-barnacle openclaw-barnacle Bot removed the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jul 4, 2026
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. labels Jul 4, 2026
@Bartok9

Bartok9 commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review — the PR body already includes redacted real behavior proof for the exact reported scenario: a stage-then-fs.rename() run across two genuinely distinct filesystems (managed git root on a separate APFS volume vs os.tmpdir(), confirmed distinct st_dev via stat -f dev). It shows the pre-fix staging location failing with EXDEV: cross-device link not permitted and the post-fix intra-device rename succeeding. The 'Real behavior proof' CI check is also green on the latest commit. Please re-review against the current body.

@clawsweeper

clawsweeper Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@Bartok9

Bartok9 commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review — the requested real behavior proof is already in the PR body under "Real behavior proof (cross-filesystem rename)".

It's a redacted terminal transcript reproducing the exact failure and fix on two genuinely distinct filesystems (managed git root on a separate APFS volume vs os.tmpdir() on the main volume, confirmed via distinct st_dev):

  • OLD (stage under os.tmpdir()): rename FAILED: EXDEV: cross-device link not permitted — the exact reported container failure.
  • NEW (stage under managed git root): rename OK — intra-device rename succeeds.

This exercises the same stage-then-fs.rename() path replaceDirectoryAtomic uses, so it demonstrates the reported EXDEV across mounts and confirms the same-filesystem staging fix resolves it. Please re-review against the current body.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 4, 2026
@vincentkoc vincentkoc self-assigned this Jul 5, 2026
@vincentkoc
vincentkoc force-pushed the fix/99885-git-plugin-exdev branch from b8e86a1 to aea559f Compare July 5, 2026 02:49
@clawsweeper

clawsweeper Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@vincentkoc
vincentkoc force-pushed the fix/99885-git-plugin-exdev branch 2 times, most recently from 2b5f160 to f2cdc7a Compare July 5, 2026 03:46
Git plugin installs cloned into os.tmpdir() then atomically renamed the
staged repo into ~/.openclaw/git/. When /tmp and the state dir live on
different filesystems (common in Docker with bind-mounted volumes), the
rename failed with EXDEV: cross-device link not permitted.

Stage the clone under the managed git root (same filesystem as the
destination) so the final rename never crosses devices. Falls back to
os.tmpdir() when the managed root cannot be prepared, preserving prior
behavior.

Closes openclaw#99885
@vincentkoc
vincentkoc force-pushed the fix/99885-git-plugin-exdev branch from 573cedb to 7f51687 Compare July 5, 2026 04:05
@clawsweeper clawsweeper Bot removed proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jul 5, 2026
@Bartok9
Bartok9 force-pushed the fix/99885-git-plugin-exdev branch from 7f51687 to 43ca9a0 Compare July 5, 2026 04:10
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jul 5, 2026
@vincentkoc
vincentkoc merged commit 5dff031 into openclaw:main Jul 5, 2026
231 of 237 checks passed
@vincentkoc

Copy link
Copy Markdown
Member

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 5, 2026
…DEV (openclaw#99896)

* fix(plugins): stage git plugin clone on target filesystem to avoid EXDEV

Git plugin installs cloned into os.tmpdir() then atomically renamed the
staged repo into ~/.openclaw/git/. When /tmp and the state dir live on
different filesystems (common in Docker with bind-mounted volumes), the
rename failed with EXDEV: cross-device link not permitted.

Stage the clone under the managed git root (same filesystem as the
destination) so the final rename never crosses devices. Falls back to
os.tmpdir() when the managed root cannot be prepared, preserving prior
behavior.

Closes openclaw#99885

* fix(plugins): isolate git clone staging per install

---------

Co-authored-by: Vincent Koc <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EXDEV: cross-device link error during git plugin install in Docker containers

2 participants