Summary
Validate sparse-checkout and skip-worktree omissions before Crabbox acquires, resolves, prepares, or borrows an SSH lease for an ordinary local sync. Keep the existing post-acquisition manifest rebuild so files created during acquisition still participate, and make the rejection name the supported recovery paths.
Problem to solve
Crabbox correctly fails closed when a tracked path is hidden by sparse-checkout or skip-worktree state but remains in the effective sync manifest. sync-plan detects that condition locally, but the error does not explain how to recover. A sync-enabled run discovers the same local condition only after provisioning and preparing a lease.
This reproduces against exact current main:
git clone https://github.com/openclaw/crabbox.git
cd crabbox
git checkout 58323474be23508b971c5e5f1fb3eb43dd6b97af
go build -trimpath -o bin/crabbox-main ./cmd/crabbox
crabbox_main="$PWD/bin/crabbox-main"
repro_dir="$(mktemp -d)"
cd "$repro_dir"
git init
git config user.name "Crabbox sparse repro"
git config user.email "repro@example.invalid"
mkdir -p visible hidden
printf 'visible\n' > visible/keep.txt
printf 'hidden\n' > hidden/drop.txt
git add visible/keep.txt hidden/drop.txt
git commit -m "test: add sparse fixture"
git sparse-checkout init --cone
git sparse-checkout set visible
"$crabbox_main" sync-plan
sync-plan exits 6 with:
build sync file list: tracked path "hidden/drop.txt" is hidden by sparse checkout or skip-worktree state but remains in sync manifest scope
The message identifies the safety condition but does not say that the checkout can be materialized or the hidden path can be removed from the effective manifest with sync.include, sync.exclude, or .crabboxignore.
Running the equivalent workload provisions before returning the same local error:
"$crabbox_main" run --provider local-container --target linux -- true
The exact-main reproduction spent 23.5 seconds creating an Ubuntu 26.04 ARM64 container, waiting for SSH, and printing a run context before releasing the lease and exiting 6. The user command never ran. The lease cleanup completed successfully.
Two controls restore the local plan. git sparse-checkout disable materializes both tracked paths and makes sync-plan report two candidate files. Keeping the checkout sparse while excluding hidden/drop.txt through .crabboxignore also succeeds and reports only the visible tracked file plus the root ignore file.
Proposed behavior
Before Crabbox mutates provider state for an ordinary Git-backed sync, run a bounded local validation that detects in-scope sparse-checkout and skip-worktree omissions. If validation fails, return the existing exit class and path evidence without acquiring, resolving, preparing, or borrowing a lease.
Keep rebuilding the authoritative manifest after acquisition. The current post-acquisition rebuild includes files created while a lease is being acquired, and the new preflight must not turn that throwaway validation result into the transfer manifest.
Make the rejection actionable. It should direct the user to materialize a full checkout, or to remove the hidden path from the effective manifest with an include or ordered exclude when that path is intentionally outside the remote workload. A concise documentation link is enough if the CLI text remains bounded.
Acceptance criteria
- A Git checkout with an in-scope hidden tracked file exits 6 before any new SSH lease acquisition or ready-pool borrow.
- Existing-lease routes reject the same local input before provider resolve or prepare work that is unnecessary for the failed sync.
- The diagnostic retains the affected path and explains both supported recovery categories: materialize the checkout, or remove the path from effective sync scope.
sync-plan and sync-enabled run use consistent recovery guidance for the same hidden path.
- Hidden paths outside
sync.include or removed by ordered excludes remain accepted.
- A sparse configuration that materializes every tracked path remains accepted.
- The authoritative manifest is still rebuilt after successful acquisition, and files created during acquisition remain included.
--no-sync, fresh remote checkouts, delegated providers that own synchronization, and other paths that do not use the ordinary local manifest retain their current behavior.
- A failed local sparse preflight creates no provider lease, container, or ready-pool side effect.
- Focused tests cover new acquisition, existing-lease preparation, ready-pool borrowing, actionable error text, excluded hidden paths, fully materialized sparse checkouts, and the post-acquisition rebuild control.
Affected area
- Local sync-manifest validation in
internal/cli/repo.go.
- Pre-acquisition routing and the post-acquisition manifest rebuild in
internal/cli/run.go.
- Sparse-manifest and run-ordering coverage in
internal/cli/repo_test.go and internal/cli/run_test.go.
- Sparse-checkout recovery guidance in
docs/commands/sync-plan.md and docs/features/sync.md.
Non-goals
- Syncing an in-scope tracked file whose contents are absent locally.
- Automatically disabling sparse checkout or changing Git index state.
- Reusing a pre-acquisition file list as the final transfer manifest.
- Changing ordered include or exclude semantics.
- Adding arbitrary non-Git directory synchronization, which remains tracked separately in issue #1334.
- Weakening the fail-closed protection added for sparse and
skip-worktree omissions.
Alternatives considered
Document the recovery only
Documentation would help users who already know where to look, but run would still provision a lease for a locally knowable error. The existing non-Git sync preflight already establishes that local manifest-source failures can stop before provider work.
Build the final manifest before acquisition
This avoids duplicate local work but creates a stale transfer list. Current regression coverage requires files created during acquisition to appear in the final sync. A bounded validation followed by the existing rebuild preserves both behaviors.
Materialize the checkout automatically
Changing sparse-checkout or index state mutates the user's Git workspace and can pull a much larger tree than intended. Crabbox should explain the choice and leave that mutation to the caller.
Supporting context
The complete 65-commit range from the release baseline through exact current main, all 24 open issues, all 16 open pull requests, every open issue authored by coygeek, and relevant closed issues and merged pull requests were checked. No current item covers pre-acquisition sparse-scope validation plus actionable recovery guidance.
Merged pull request #1241 added the exact fail-closed sparse-manifest protection and deliberately preserved post-acquisition manifest behavior. Merged pull request #1253 is the closest lifecycle precedent: it made the non-Git manifest-source error actionable and rejected that locally knowable condition before provider work while still rebuilding the transfer manifest later. Neither change covers the sparse-scope ordering and recovery gap above.
Summary
Validate sparse-checkout and
skip-worktreeomissions before Crabbox acquires, resolves, prepares, or borrows an SSH lease for an ordinary local sync. Keep the existing post-acquisition manifest rebuild so files created during acquisition still participate, and make the rejection name the supported recovery paths.Problem to solve
Crabbox correctly fails closed when a tracked path is hidden by sparse-checkout or
skip-worktreestate but remains in the effective sync manifest.sync-plandetects that condition locally, but the error does not explain how to recover. A sync-enabledrundiscovers the same local condition only after provisioning and preparing a lease.This reproduces against exact current
main:sync-planexits 6 with:The message identifies the safety condition but does not say that the checkout can be materialized or the hidden path can be removed from the effective manifest with
sync.include,sync.exclude, or.crabboxignore.Running the equivalent workload provisions before returning the same local error:
The exact-main reproduction spent 23.5 seconds creating an Ubuntu 26.04 ARM64 container, waiting for SSH, and printing a run context before releasing the lease and exiting 6. The user command never ran. The lease cleanup completed successfully.
Two controls restore the local plan.
git sparse-checkout disablematerializes both tracked paths and makessync-planreport two candidate files. Keeping the checkout sparse while excludinghidden/drop.txtthrough.crabboxignorealso succeeds and reports only the visible tracked file plus the root ignore file.Proposed behavior
Before Crabbox mutates provider state for an ordinary Git-backed sync, run a bounded local validation that detects in-scope sparse-checkout and
skip-worktreeomissions. If validation fails, return the existing exit class and path evidence without acquiring, resolving, preparing, or borrowing a lease.Keep rebuilding the authoritative manifest after acquisition. The current post-acquisition rebuild includes files created while a lease is being acquired, and the new preflight must not turn that throwaway validation result into the transfer manifest.
Make the rejection actionable. It should direct the user to materialize a full checkout, or to remove the hidden path from the effective manifest with an include or ordered exclude when that path is intentionally outside the remote workload. A concise documentation link is enough if the CLI text remains bounded.
Acceptance criteria
sync-planand sync-enabledrunuse consistent recovery guidance for the same hidden path.sync.includeor removed by ordered excludes remain accepted.--no-sync, fresh remote checkouts, delegated providers that own synchronization, and other paths that do not use the ordinary local manifest retain their current behavior.Affected area
internal/cli/repo.go.internal/cli/run.go.internal/cli/repo_test.goandinternal/cli/run_test.go.docs/commands/sync-plan.mdanddocs/features/sync.md.Non-goals
skip-worktreeomissions.Alternatives considered
Document the recovery only
Documentation would help users who already know where to look, but
runwould still provision a lease for a locally knowable error. The existing non-Git sync preflight already establishes that local manifest-source failures can stop before provider work.Build the final manifest before acquisition
This avoids duplicate local work but creates a stale transfer list. Current regression coverage requires files created during acquisition to appear in the final sync. A bounded validation followed by the existing rebuild preserves both behaviors.
Materialize the checkout automatically
Changing sparse-checkout or index state mutates the user's Git workspace and can pull a much larger tree than intended. Crabbox should explain the choice and leave that mutation to the caller.
Supporting context
v0.46.0at8ba71f913bbe57285ae29af45ef0d8ec6712477d.main:58323474be23508b971c5e5f1fb3eb43dd6b97af.a2e42cd24c453fffa6b51cb074ba311632a166acf3dad6308b4266f401ed615e.local-container.The complete 65-commit range from the release baseline through exact current
main, all 24 open issues, all 16 open pull requests, every open issue authored bycoygeek, and relevant closed issues and merged pull requests were checked. No current item covers pre-acquisition sparse-scope validation plus actionable recovery guidance.Merged pull request #1241 added the exact fail-closed sparse-manifest protection and deliberately preserved post-acquisition manifest behavior. Merged pull request #1253 is the closest lifecycle precedent: it made the non-Git manifest-source error actionable and rejected that locally knowable condition before provider work while still rebuilding the transfer manifest later. Neither change covers the sparse-scope ordering and recovery gap above.