From 3963d38560824feefba1e6da9265c3b50846e5e7 Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Mon, 9 Nov 2026 04:42:05 -0600 Subject: [PATCH] test(chaos): drop the R1 clone's remote so agents cannot push to the operator's checkout (#20228) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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://`. 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 `. 2. In a task worktree, run `git remote get-url origin`. It prints `file://`. **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 --- tests/restart-chaos/seed.test.ts | 5 +++++ tests/restart-chaos/seed.ts | 1 + 2 files changed, 6 insertions(+) diff --git a/tests/restart-chaos/seed.test.ts b/tests/restart-chaos/seed.test.ts index 24891ee091d..d6a7a920d0a 100644 --- a/tests/restart-chaos/seed.test.ts +++ b/tests/restart-chaos/seed.test.ts @@ -122,6 +122,11 @@ describe("cloneWorkspaceRepository", () => { const pinned = cloneWorkspaceRepository(source, path.join(root, "pinned"), 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/); + 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 { await rm(root, { recursive: true, force: true }); } diff --git a/tests/restart-chaos/seed.ts b/tests/restart-chaos/seed.ts index ee6783e9efd..2ce62f5ac18 100644 --- a/tests/restart-chaos/seed.ts +++ b/tests/restart-chaos/seed.ts @@ -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 }); } } + git("remote", "remove", "origin"); return directory; }