mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-10 12:37:55 +01:00
fix(ssh-restore): reset the local index to HEAD after importing remote git history (#16644)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents can run on a remote SSH environment. After the run, Paperclip
restores the workspace and imports the remote git history.
> - The import moves the checked-out branch with `git update-ref`.
`update-ref` does not change the index.
> - The index then still matches the old tip. `git status` in the
workspace shows the new files of the run as staged deletions.
> - An operator who commits in that workspace can remove the work of the
agent by mistake.
> - The sandbox restore already resets the index after the import
(#8595). The SSH restore does not.
> - This pull request calls the same `resetLocalGitIndexToHead` after
the SSH restore.
> - The benefit is a clean `git status` after each SSH run, the same as
after a sandbox run.
## Linked Issues or Issue Description
Refs #8595 (the same fix for the sandbox restore). Refs #15662, Refs
#16440 (other open changes to the same SSH restore code).
**What happened?**
After a remote SSH run imported a commit, `git status` in the local
workspace showed every new file of that commit as a staged deletion, and
every changed file as a staged modification. The files on disk were
correct.
**Expected behavior**
`git status` is clean after the import, because the working tree and
HEAD are the commit of the run.
**Steps to reproduce**
1. Use an opencode_local agent on an SSH environment with a git_worktree
project workspace.
2. Let the agent add a file and commit on the remote copy.
3. After the run, run `git status` in the local worktree. The new file
shows as `D` in the index.
**Paperclip version or commit**
paperclipai 2026.916.1 (also present on master 272316808)
## What Changed
- `packages/adapter-utils/src/ssh.ts`: the SSH restore merge gets an
`afterApply` that calls `resetLocalGitIndexToHead`, when remote git
history was imported. The sandbox restore does the same since #8595.
- `packages/adapter-utils/src/ssh-fixture.test.ts`: "round-trips remote
git commits through the managed runtime restore path" now also checks
that nothing is staged after the restore.
## Verification
- I ran the SSH fixture tests in a container with `sshd` (they skip on a
host without it): `ssh-fixture.test.ts` and
`git-workspace-sync.test.ts`, 87 tests pass.
- Without the change, the managed runtime restore test leaves `M
tracked.txt` staged (`git diff --cached`) and `git status` shows `MM
tracked.txt`. With the change, nothing is staged and `git status` shows
` M tracked.txt`, which is the uncommitted remote edit.
- `npx tsc --noEmit -p packages/adapter-utils` passes.
- I found the bug on a real SSH environment (opencode_local in a runner
container, paperclipai 2026.916.1).
## Risks
Low risk. It reuses the index reset that the sandbox restore already
uses. It runs only when remote git history was imported. Work staged on
the host before the run is reset to HEAD, the same as in the sandbox
path.
## Model Used
Claude Opus 5.5 (claude-opus-5-5), Anthropic, used through Claude Code
with tool use (shell, file edits). Human-reviewed.
## 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
- [ ] I will address all Greptile and reviewer comments before
requesting merge
This commit is contained in:
1 parent
0189fedffc
commit
576d6f6430
2 files changed
+10
No files matched your search
@@ -1153,6 +1153,8 @@ describe("ssh env-lab fixture", () => {
|
||||
|
||||
expect(await git(localRepo, ["log", "-1", "--pretty=%s"])).toBe("remote update");
|
||||
await expect(readFile(path.join(localRepo, "tracked.txt"), "utf8")).resolves.toBe("dirty remote\n");
|
||||
// The index follows the imported HEAD: only the remote's uncommitted edit shows, unstaged.
|
||||
expect(await git(localRepo, ["diff", "--cached", "--name-status"])).toBe("");
|
||||
}, SSH_FIXTURE_TEST_TIMEOUT_MS);
|
||||
|
||||
it("propagates remote commits to the local worktree with no git remote configured (no-remote-git contract)", async () => {
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
PROJECT_REPOSITORIES_DIR,
|
||||
PROJECT_REPOSITORY_STAGING_PATTERN,
|
||||
readSanitizedOriginRemoteUrl,
|
||||
resetLocalGitIndexToHead,
|
||||
} from "./git-workspace-sync.js";
|
||||
import type { RunProcessResult } from "./server-utils.js";
|
||||
import type { DirectorySnapshot } from "./workspace-restore-merge.js";
|
||||
@@ -1800,6 +1801,13 @@ async function restoreWorkspaceRootFromSsh(input: {
|
||||
});
|
||||
}
|
||||
: undefined,
|
||||
// integrateImportedGitHead moves the branch with update-ref, which leaves the index
|
||||
// on the old tip. Reset it to HEAD as the sandbox restore does (#8595).
|
||||
afterApply: importedHead
|
||||
? async () => {
|
||||
await resetLocalGitIndexToHead({ localDir: input.localDir });
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
} finally {
|
||||
if (importedRef) {
|
||||
|
||||
Reference in new issue
Block a user