test(chaos): drop the R1 clone's remote so agents cannot push to the operator's checkout (#20228)

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The restart chaos harness runs real agents against a local Paperclip
and grades how they survive restarts
> - The R1 workload clones `--workspace-repo` (usually the operator's
own Paperclip checkout) and has agents change code in that clone
> - The clone kept `origin` pointing at the operator's checkout over
`file://`
> - An agent in an R1 workspace could therefore `git push` branches or
commits into the operator's real repository
> - This pull request removes the clone's remote right after the pinned
checkout
> - The benefit is that agent code in an R1 wave can no longer write to
the operator's checkout

## Linked Issues or Issue Description

Refs #20111 (review thread on the R1 workload)

**What happened?**
A review of #20111 noted that the cloned R1 workspace still has `origin
= file://<operator checkout>`. Project worktrees created from that clone
inherit the remote, so an agent can push into the operator's repository.

**Steps to reproduce**
1. Run a wave with `--workloads R1 --workspace-repo <checkout>`.
2. In a task worktree, run `git remote get-url origin`. It prints
`file://<checkout>`.

**Expected behavior**
The workspace has no path back to the operator's checkout.

**Actual behavior**
`git push origin HEAD:refs/heads/x` from the workspace would create a
branch in the operator's checkout.

## What Changed

- `cloneWorkspaceRepository` runs `git remote remove origin` after the
clone and any pinned-commit fetch.
- The clone test asserts that both the plain and the pinned clone have
no remote, that `git push origin` fails, and that no branch appears in
the source.

## Verification

- `npx vitest run --config tests/restart-chaos/vitest.config.ts
tests/restart-chaos/seed.test.ts`: the new assertions fail before the
change (`expected 'origin' to be ''`) and pass after it (8/8).
- `node scripts/typecheck-test-projects.mjs tests/restart-chaos`: clean.
- Audit of the three R1 waves that already ran (w-20261108T215822-s9805,
w-20261109T030542-s9805, w-20261109T081456-s9901): no reflog entry of
type push on any local ref in the operator checkout during the wave
windows, no `CHA-*` or R1 branch there, unchanged hooks, and no push in
the kept clones' logs. Nothing was pushed.
- A short local R1 wave on this branch confirms workspace provisioning
works without the remote (result in a comment below).

## Risks

- If Paperclip workspace provisioning needed `origin` in the project
repository, R1 tasks would fail to provision. The confirming R1 wave
covers this.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, with tool use and code
execution in Claude Code.

## 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
- [x] 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)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DottaandClaude Opus 5.5 authored and GitHub committed 2026-11-09 04:42:05 -06:00
1 parent 780f378f60
commit 3963d38560
2 files changed
+6

No files matched your search

+5
View File
@@ -122,6 +122,11 @@ describe("cloneWorkspaceRepository", () => {
const pinned = cloneWorkspaceRepository(source, path.join(root, "pinned"), first); const pinned = cloneWorkspaceRepository(source, path.join(root, "pinned"), first);
expect(execFileSync("git", ["rev-parse", "HEAD"], { cwd: pinned, encoding: "utf8" }).trim()).toBe(first); expect(execFileSync("git", ["rev-parse", "HEAD"], { cwd: pinned, encoding: "utf8" }).trim()).toBe(first);
expect(() => cloneWorkspaceRepository(source, path.join(root, "missing"), "0".repeat(40))).toThrow(/no longer has commit/); expect(() => cloneWorkspaceRepository(source, path.join(root, "missing"), "0".repeat(40))).toThrow(/no longer has commit/);
for (const copy of [clone, pinned]) {
expect(execFileSync("git", ["remote"], { cwd: copy, encoding: "utf8" }).trim()).toBe("");
expect(() => execFileSync("git", ["push", "--quiet", "origin", "HEAD:refs/heads/agent-push"], { cwd: copy, stdio: "pipe" })).toThrow();
}
expect(execFileSync("git", ["branch", "--list", "agent-push"], { cwd: source, encoding: "utf8" }).trim()).toBe("");
} finally { } finally {
await rm(root, { recursive: true, force: true }); await rm(root, { recursive: true, force: true });
} }
+1
View File
@@ -56,6 +56,7 @@ export function cloneWorkspaceRepository(source: string, directory: string, comm
throw new Error(`--workspace-repo ${source} no longer has commit ${commit} that this wave used`, { cause: error }); throw new Error(`--workspace-repo ${source} no longer has commit ${commit} that this wave used`, { cause: error });
} }
} }
git("remote", "remove", "origin");
return directory; return directory;
} }