mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-09 07:10:34 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - A task workspace can hold managed nested repositories in `.paperclip-repositories/<name>`. Sandbox and SSH runs read each nested repository before they stage the workspace. > - The server stages a new nested clone in `.paperclip-repositories/<checkout>.clone-XXXXXX` and renames it when the clone is complete. If the process stops during the clone, this staging directory stays. > - Workspace preparation keeps `.clone-` entries on purpose. But the snapshot reader and the SSH listing reject every directory name that has a dot. One leftover staging directory makes every later sandbox or SSH run on that workspace fail with `Invalid project repository directory`. > - This pull request makes both readers skip only server-owned `<name>.clone-<suffix>` directories. Sandbox and SSH staging also exclude these directories from the upload and from the restore. > - The benefit is that an interrupted clone no longer blocks all later runs on the workspace. Other unexpected entries still fail closed. ## Linked Issues or Issue Description Refs #17382 **What happened?** A hosted staging run failed before the agent started. The error was `Error: Invalid project repository directory` from `readManagedWorkspaceRepositories`, through `readGitWorkspaceSnapshot` in `prepareSandboxManagedRuntime`. The server had restarted about five minutes before the run. The run was an automatic retry. **Expected behavior** A leftover clone staging directory from an interrupted clone does not stop later runs. The sandbox or SSH host does not receive the partial clone. The restore does not delete or change the host staging directory, because a concurrent preparation can own it. An unexpected directory that is not a staging directory still fails closed. **Steps to reproduce** 1. Create a Git workspace with a managed nested repository at `.paperclip-repositories/backend`. 2. Create the directory `.paperclip-repositories/backend.clone-Ab12Cd` with a `.git` directory and a file in it. This is the state after an interrupted clone. 3. Call `readGitWorkspaceSnapshot` on the workspace, or start a sandbox or SSH run on it. 4. Before this change, the call throws `Invalid project repository directory`. **Additional context** `materializeManagedProjectWorkspace` creates the staging directory with `fs.mkdtemp(\`${cwd}.clone-\`)`. Its cleanup does not run when the process stops. `prepareProjectRepositoryWorkspaces` already skips entries that contain `.clone-` when it retires stale entries. #17382 moved the common nested path to shared worktrees, but the fallback path still stages in place. #18534 protects nested `.git` directories during copy-back. It does not cover staging directories. ## What Changed - `git-workspace-sync.ts`: Add `PROJECT_REPOSITORY_STAGING_PATTERN` (`^[a-zA-Z0-9_-]+\.clone-[a-zA-Z0-9_-]+$`). `readManagedWorkspaceRepositories` skips a directory that matches it. Add `listProjectRepositoryStagingPaths`, which lists these directories. - `ssh.ts`: `listLocalProjectRepositories` skips the same directories. `prepareWorkspaceForSshExecution` excludes the staging paths from the upload when no completed repository exists, and returns them as `excludedPaths`. - `remote-managed-runtime.ts`: Add `excludedPaths` to the SSH workspace baseline excludes. The restore does not download them, and the merge does not change them on the host. - `sandbox-managed-runtime.ts`: Add the listed staging paths to the workspace archive excludes and to the restore excludes. - New test file `managed-clone-staging.test.ts`: - The snapshot and the SSH listing skip the staging directory and keep the nested repository. - An unexpected entry such as `backend.backup` still fails in both readers. - A sandbox round trip does not ship the staging directory, restores the nested repository change, and keeps the host staging directory. - An SSH round trip with only a staging directory does not ship it, restores a tracked-file change, and keeps the host staging directory and its `.git/HEAD`. ## Verification - `cd packages/adapter-utils && PATH=$PATH:/usr/sbin npx vitest run src/managed-clone-staging.test.ts`: 4 pass, with a real sshd. - Red: with the source changes reverted, the staging tests fail. Before the change, the reader fails with the exact production message `Invalid project repository directory`. Before the SSH fix, the SSH round trip failed with `expected 'present' to be 'absent'`. Without the restore exclude, the sandbox restore deletes the host staging directory. - `npx vitest run src/ssh-fixture.test.ts src/remote-managed-runtime.test.ts src/ssh-command-managed.test.ts src/managed-clone-staging.test.ts src/git-workspace-sync.test.ts src/sandbox-managed-runtime.test.ts`: 218 pass. `ssh-fixture.test.ts` ran all 25 tests with a real sshd. 3 tests in `git-workspace-sync.test.ts` fail (two "rejects a checkout during integration" cases and "does not inherit a poisoned GIT_CONFIG_GLOBAL"). They fail in the same way on unmodified master in the test sandbox, so they are environment failures. - `npx tsc --noEmit -p packages/adapter-utils`: passes. ## Risks - Low risk. The skip matches only the exact staging name shape that the server creates. Every other unexpected name still fails closed. - The excludes apply only to directories that exist when staging starts. A staging directory that appears during the run is not in the baseline, so the restore does not touch it. - A user directory with a name like `name.clone-x` in `.paperclip-repositories` is now ignored and not rejected. This directory belongs to the server, so this is the expected behavior. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - Anthropic Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use (shell, file edits, test runs, GitHub connector). ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code)