## Thinking Path > - Paperclip orchestrates AI agents across isolated execution workspaces; the local cwd is the only persistence boundary between runs. > - Workspace lifecycle (worktree_prepare → execute → workspace_finalize) and the wake/accept flow are what guarantee that dependent issues see a consistent worktree. > - PAPA-380 / PAPA-431 / PAPA-432 / PAPA-440 surfaced three holes in that contract: silent env reuse across assignees, dependent wakes firing before finalize, and `issue.interaction.accept` advancing before finalize landed. > - PAPA-441 / PAPA-442 then needed to document the "no remote git" contract and prevent future adapter/runtime code from quietly reintroducing `git push` as a backdoor sync. > - This pull request lands those server fixes, the static `check-no-git-push` enforcement, the AUTHORING.md cross-link, and the Cody-review follow-ups on the PAPA-430 thread. > - The benefit is that finalize is a real barrier — board accepts, dependent wakes, and operator-set env all respect it — and adapter code can't bypass it via raw `git push`. ## What Changed - **server (PAPA-380, PAPA-431):** `execution-workspace-policy` refuses silent env reuse when the assignee's resolved env disagrees with the workspace it would inherit. The inheritance protection is now scoped to the actual inheritance signal — explicit issue-level `environmentId` is honored even when the agent's default env is `null`. - **server (PAPA-432):** `heartbeat.ts` gates dependent wakes on `listUnfinalizedExecutionWorkspaceIds`, and writes a `workspace_finalize` row on the succeeded path. Write failures now surface instead of being swallowed so dependents aren't silently stranded behind a missing row. - **server (PAPA-440):** `issue-thread-interactions.acceptInteraction` adds a workspace_finalize precondition for `request_confirmation` (not `suggest_tasks`). Accept returns 409 if finalize hasn't succeeded for the latest workspace operation. - **ci (PAPA-442):** new `scripts/check-no-git-push.mjs` static check scans `packages/adapters/`, `packages/adapter-utils/`, `server/src/`, and `cli/src/` for any `git push` invocation (string or args-array). Wired into the `policy` PR job and `test:release-registry`. Operators can opt in per-call with `// paperclip:allow-git-push: <reason>`. Release scripts are out of scope by design. - **docs (PAPA-441):** `AUTHORING.md` documents the no-remote-git contract and cross-links the static check so adapter authors learn the rule and the enforcement together. - **review follow-up (PAPA-430, Cody):** three fixes — env resolver bug, accept-gate scope (request_confirmation only), and finalize record write on the succeeded path. ## Verification - `pnpm exec vitest run server/src/__tests__/execution-workspace-policy.test.ts server/src/__tests__/issue-thread-interactions-service.test.ts` → 33/33 pass - `node scripts/check-no-git-push.test.mjs` → check covers string form, args-array form, comment exclusions, and per-line allow-comment. - Manual: server compiles; the policy job runs the check in <1s before heavier jobs. ## Risks - **Behavioral shift in accept:** boards accepting `request_confirmation` while finalize is in-flight now get 409s. This is intentional — they can retry — but it changes timing on a hot path. `suggest_tasks` is unaffected. - **Workspace policy:** the env-reuse refusal is a new error path. Issues that previously silently reused an env from a different-assignee workspace will now fail-loud; the resolver still honors explicit issue-level `executionWorkspaceSettings.environmentId`. - **CI rule:** any future legitimate `git push` in scoped dirs must be marked with the allow-comment, which is the intended ergonomic. ## Model Used - Claude Opus 4.7 (`claude-opus-4-7`, extended thinking), via Claude Code in the Paperclip executor adapter. ## 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 run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] If this change affects the UI, I have included before/after screenshots (N/A — server/CI/docs only) - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge Closes related issues: PAPA-430, PAPA-380, PAPA-431, PAPA-432, PAPA-440, PAPA-441, PAPA-442 --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
3.2 KiB
Adapter Authoring Notes
In-repo notes for adapter authors. The user-facing guide lives at
docs/adapters/creating-an-adapter.md;
this file holds invariants that are easy to violate from inside the adapter
package itself.
No-remote-git contract (cross-run persistence)
The local execution-workspace cwd is the only persistence boundary across runs. No adapter may depend on a git remote for cross-run state.
Why: Paperclip resolves a local execution workspace (a worktree) for each
heartbeat. Code state is carried forward by syncing that local cwd to wherever
the agent actually runs — over ssh, into a sandbox, into a managed runtime —
and then syncing changes back when the run finishes. Treating a git remote
as the source of truth (git push from inside the agent, fetch on the next
wake) breaks dependent issues that are gated on the local worktree being
caught up, and breaks isolated execution workspaces that have no remote
configured at all.
How to apply:
- Never
git pushfrom adapter runtime code. Never assume the local worktree has anygit remoteconfigured. If you need data from the previous run, read it from the local cwd Paperclip handed you. - If your adapter runs the agent on a different host (ssh, sandbox, remote
container), use the round-trip helpers in
@paperclipai/adapter-utils:prepareWorkspaceForSshExecutionbundles the local cwd to the remote dir before the run, andrestoreWorkspaceFromSshExecutionsyncs remote-side changes (including new git commits) back into the local cwd after the run. Both run with nogit remoteconfigured. - If your adapter runs the agent locally, you can read and write the cwd
directly — same invariant applies: changes that future runs need must live
in the local cwd by the time
execute()returns. - A failed sync-back is a run-level error. The heartbeat records
workspace_finalize=failedon the execution workspace, which gates dependent issue wakes until the next successful finalize. Do not swallow restore errors.
The invariant is pinned by the no-remote-git contract case in
packages/adapter-utils/src/ssh-fixture.test.ts,
which asserts that a remote-only commit propagates to the local worktree
through prepareWorkspaceForSshExecution → restoreWorkspaceFromSshExecution
with no git remote configured at any point.
A static check enforces the rule before runtime ever sees it:
scripts/check-no-git-push.mjs scans
adapter and runtime source (packages/adapters/, packages/adapter-utils/,
server/src/, cli/src/) and fails the policy CI job if any unapproved
git push invocation is added. If you are building an operator-configured
path that legitimately must push, add a
// paperclip:allow-git-push: <reason> comment on the line (or the line
above) so the opt-in shows up in code review.
For the architecture-level write-up of cross-run persistence, see
docs/guides/board-operator/execution-workspaces-and-runtime-services.md.