## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Task controls coordinate provider execution and queued user messages. > - Stop could finish before an embedded ACP provider stopped its tools. > - A later request could be held for reconciliation without a clear task response. > - A restored provider could also retain the stopped run's API credential. > - This pull request verifies provider termination and preserves safe session continuation. > - Operators can continue known-safe work and see why uncertain work cannot start. ## Linked Issues or Issue Description **What happened?** Stop could leave an embedded ACP provider running. A queued follow-up followed by “go” could fail before it reached the provider. Task chat could show a generic missing-response message. Even a restored session could use the previous run's credential and fail its task update. **Expected behavior** Stop waits for confirmed provider termination. A later explicit wake continues the same compatible session only when recorded actions have known outcomes. It carries pending comments and the current run's environment. Uncertain actions retain a visible reconciliation hold. Composer Stop preserves the existing pause rule: conversation can continue while paused, but task work requires Resume. **Steps to reproduce** 1. Start an embedded ACP task. 2. Send a second request while the provider is running. 3. Interrupt the run, then send “go”. Also test composer Stop followed by Resume work. 4. Check that the request is delivered once and that the provider can complete the task through the current run's API credential. 5. Repeat with an unfinished write. Confirm that the write stops and that further execution stays blocked with a visible reason. **Paperclip version or commit** Built from source on master at `3bc60dd8b` plus this branch. **Deployment mode** Local source build with an isolated embedded PostgreSQL instance. Refs #11183. Refs #12552. Those changes address recovery after operator cancellation. This change also covers embedded ACP termination, session proof, pending-comment delivery, and task feedback. ## What Changed - Propagate Stop into embedded ACP and wait for bounded adapter cleanup and provider exit. Retain the actual ChildProcess object for forced termination on all platforms; never signal a recycled numeric PID. - Preserve interrupted checkpoints only for acknowledged, local, persistent sessions with settled reads or no tools. Keep writes, incomplete actions, and forced termination blocked. - Restore the same compatible provider session with the current run's environment. Reject fresh-session fallback for an interrupted checkpoint. - Adopt pending comments on the next explicit wake. Stop alone does not dispatch them. - Share the execution-blocker rule across dispatch, Resume, and task detail. Show Stopped or Couldn't start with the recorded reason. Resolve the stopped agent for the run link, including reviewer runs. - Keep execution reconciliation holds intact when generic recovery sees queued comments or healthy child tasks. - Add process, service, component, and browser regression coverage. Fix disposable database cleanup and React test settling exposed by the full suite. ## Verification - Passed `pnpm -r typecheck`, `pnpm build`, and `pnpm check:token-gates`. - Passed all three `acp-stop-continuation.spec.ts` browser journeys. They use an actual ACP child process and require task completion through the agent API. - Passed 165 adapter execution, operator-stop, and child-process control tests, 17 queued-comment route tests, and 65 tests in the two adjusted UI suites. Earlier focused recovery, heartbeat, and task-control tests also passed. - Manually used the browser to queue a request, Stop, send “go” while paused, and Resume. The same session answered once and moved the task to Done with the current run's credential. - Manually interrupted an unfinished write. Its file size stayed fixed for five seconds. “Go” showed the reconciliation reason and did not start another provider prompt. - Separate live Claude ACP smoke checks confirmed that Stop ended a disposable local write and that a no-tool interruption could resume the exact provider session. The browser fixture does not call Drive or another external app. - Passed all 5,615 UI tests and 3,090 other workspace tests. The CLI and general server groups pass with targeted retries: two transient server failures passed together on retry, and two embedded-database startup failures passed after removing abandoned shared-memory segments from this task's completed browser fixtures. All 144 serialized server suites completed, with 2,189 tests passing after two transient HTTP socket failures passed on retry. - Passed all 135 heartbeat process/recovery tests, including a deterministic regression that failed before the recovery-sweep fix. - Passed 18 dispatch integration tests, including stopped-reviewer links, company boundaries, and malformed run IDs. - Greptile is 5/5 on `7dd170d83`, with zero unresolved review threads. The security scan and all required CI gates pass for the same commit. ## Risks - Safe continuation depends on complete tool reporting and a restorable local provider session. Unknown outcomes remain blocked and require reconciliation. - Provider cleanup can take time. A timeout does not grant replay permission. - The change adds optional adapter context fields and an optional issue projection. It does not change the database schema or require a migration. - Test cleanup truncates company data only in a disposable test database. ## Model Used OpenAI GPT-6, running as Codex with repository tools, code execution, and browser interaction. The runtime does not expose a more specific model deployment ID or context-window size. ## 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 - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
11 KiB
Composer Stop and task controls
The empty composer shows Stop while this task has a live execution and the viewer can manage task controls. Stop creates the same manual pause hold as Pause work / Pause subtree in the task menu. A parent pause includes its descendants. Cancellation remains a separate menu action.
Text (after trimming) or attachments switch the button back to Send. Uploading and failed attachments retain their existing restrictions. Keyboard submission never invokes Stop. Queued-message editing and structured interactions keep their existing actions, and text entered while stopping remains in the draft.
Pause dispatches without a preview dialog or reason. The button stays pending while affected run state is checked; native cancellation must be acknowledged. A saved hold with unconfirmed termination produces an error rather than a success claim. The cancellation dialog requires a valid preview and excludes terminal tasks. Resume/restore retain the optional wake-agents checkbox.
Resume and execution recovery
Resume releases a hold. Waking agents is optional and only applies to tasks in
todo, in_progress, or in_review; parked and terminal tasks stay untouched.
The current execution-recovery policy requires verified outcomes before a stopped
provider can restart. If any affected task still needs that reconciliation,
Resume with wake enabled returns an inline error and preserves the pause. The
operator can release the pause without waking agents, then use the existing
execution-reconciliation flow after reviewing the stopped run. Resume does not
claim that unknown provider actions completed or were never performed.
A completed release remains successful if a best-effort wake fails. Its response
includes optional wakeFailures, the page reports them inline, and remaining
eligible tasks still receive their wake requests. No new endpoint is introduced.
The deterministic E2E fixtures prove interruption, then record their known lack
of external effects through the existing reconciliation API before continuing.
Embedded ACP also supports verified continuation of an interrupted local session. The adapter must acknowledge cancellation and prove a preserved session with settled read-only work. Stop waits for provider cleanup. Forced local termination uses the actual child-process handle captured at spawn, including on Windows. An unavailable handle does not authorize a signal or replay. Unknown actions remain blocked, and task detail shows the reason even after recovery bookkeeping resolves. A run-level Stop leaves the task unpaused; a subsequent comment can continue the same session with the earlier queued messages. Composer Stop still creates a pause hold. Human comments can receive a response within the existing paused conversation scope; task execution requires Resume. Neither path permits a fresh-session fallback when the interrupted checkpoint cannot be restored.
The credential-free ACP regression journey uses an actual ACP child process:
pnpm exec playwright test --config tests/e2e/playwright.config.ts tests/e2e/acp-stop-continuation.spec.ts
It covers the queued-follow-up sequence (queue a second request, stop, then send “go”), same-session delivery of both messages, and an unfinished write that stops mutating its file but retains a visible execution blocker. Unit and integration tests additionally cover pre-start Stop, unavailable/changed sessions, rotating scratch directories, cancellation acknowledgment, a provider that hangs during cleanup after returning cancellation, deferred-wake adoption, and company-scoped blocker lookup. Hosted-provider behavior is a separate smoke test.
The browser tests also require the continued provider to complete the task through the agent API. Restoring a session must refresh its run identity, API credential, and scratch environment. The same conversation must not reuse the stopped run's credential. A regression test checks distinct run IDs and token hashes across the restart without logging the credentials themselves.
On 2026-09-09, all three ACP browser journeys passed. A manual browser walk-through also queued a request, used composer Stop, sent “go” while paused, and selected Resume work. The pause stayed in place during the conversation reply. Resume restored the same provider session, answered the pending request once, and moved the task to Done through the current run's authenticated API call. These tests use a deterministic ACP child process; they do not call Drive or another external app. The manual unfinished-write check also confirmed that file size stayed unchanged for five seconds after Interrupt. Sending “go” displayed the reconciliation reason and did not start another provider prompt.
A separate live Claude ACP smoke test interrupted a Bash tool writing only to a disposable local file: cancellation settled in 1,167 ms, output remained unchanged for five seconds, and no matching tool process remained. The shell action correctly did not receive automatic replay permission. A second live Claude check interrupted a response with no tools, restored the exact same provider session, and received the requested follow-up answer. These are local-provider observations, not a latency guarantee or proof for every provider and remote sandbox.
Quiet task feedback
The visible task/subtree does not produce duplicate state toasts. Its live
notifications are suppressed while foregrounded, including descendant runs;
unrelated and background work retains notifications. Tree-control results use
inline state, and failures stay in the composer, page, or confirmation dialog.
The pause row contains only “Subtree is paused.” (or “Task is paused.”) and
Resume. Expected cancellation uses a muted gray disclosure with optional details.
This is recorded as a product rule in DESIGN.md.
The follow-up passed 213 focused tests, both isolated runner E2E journeys (including no-toast assertions), UI typecheck/build, token gates, and Storybook build. Paused, expanded cancellation, Stop without toasts, mobile, and light stories were inspected in the browser.
Storybook
Run from the worktree:
pnpm --filter @paperclipai/ui exec storybook dev -p 6016 -c storybook/.storybook --no-open
Open http://localhost:6016/?path=/story/tasks-execution-controls--running-empty.
The Tasks / Execution Controls stories compose the production composer and
menu/dialog controls together. They cover text switching, attachment-only,
idle, stopping, paused, errors, cancellation preview/loading, and mobile/light
presentations. The Storybook state transitions simulate requests; runner
verification belongs to the isolated browser suite below.
Automated verification
pnpm exec vitest run --project @paperclipai/ui ui/src/components/task-chat/TaskChatComposer.test.tsx ui/src/components/TaskChatThread.test.tsx ui/src/pages/IssueDetail.test.tsx ui/src/lib/wait-for-stopped-runs.test.ts
pnpm exec vitest run --project @paperclipai/server server/src/__tests__/issue-tree-control-routes.test.ts
pnpm check:token-gates
pnpm build-storybook
pnpm -r typecheck
pnpm test:run
pnpm build
The component/page tests cover whitespace, attachments and upload restrictions, permissions, keyboard submission, queue edits, duplicate clicks, draft preservation, shared pause requests, compact cancellation, and visible errors. Stop verification tests include continued execution, native acknowledgment, network failures, and hung status requests. Route tests cover pause dispatch, authorization, cancellation, and opt-in resume wakeups with terminal/company exclusions.
Isolated browser acceptance
Legacy process coverage needs no provider credentials:
pnpm exec playwright test --config tests/e2e/playwright-composer-stop.config.ts
To include native execution, build the real runner and deterministic Codex protocol fixture from this checkout, then point the suite at their absolute paths:
cargo build --manifest-path packages/paperclip-runner/runner/Cargo.toml --bin paperclip-runnerd --bin fake-codex-app-server
PAPERCLIP_STOP_FAKE_CODEX="$PWD/packages/paperclip-runner/runner/target/debug/fake-codex-app-server" \
PAPERCLIP_RUNNER_BINARY="$PWD/packages/paperclip-runner/runner/target/debug/paperclip-runnerd" \
pnpm exec playwright test --config tests/e2e/playwright-composer-stop.config.ts
The suite boots a disposable local-trusted instance on port 3199 (override with
PAPERCLIP_E2E_PORT). It never attaches to an existing server. Native coverage
is explicitly skipped without the fixture; it must not use a logged-in provider
as a fallback. Test companies are archived during cleanup.
For each runner, the journey starts a parent, child, and unrelated task, plus a terminal child. It sends while running and verifies the durable queue, clicks Stop, verifies interruption and the persisted hold, and observes three ten-second scheduler intervals without continuation. It resumes with wakeups, pauses from the menu, dismisses and then confirms cancellation, and verifies terminal-task exclusions and unrelated execution.
Timing attachments distinguish click-to-request from request-to-observed-stop.
Native proof requires provider turn/start before the click, turn/interrupt
after it, and durable cancellation acknowledgment. Legacy proof checks the
actual parent and child PIDs have exited with a one-second configured grace
period. HTTP success alone is insufficient.
For a release with real providers, repeat while an actual long-running tool is active, confirm the tool's own process/output stops, and verify a queued message is consumed on continuation. The deterministic provider exercises the native transport and cancellation protocol, not external provider behavior or every tool's process cleanup. Preserve these limits in any test report.
Recorded acceptance run (2026-09-09)
Both isolated browser journeys passed. Legacy Stop dispatched after 214 ms and observed both runs stopped 155 ms after dispatch; native dispatched after 212 ms and observed stop 320 ms later. These are single local observations including browser automation overhead, not latency guarantees. Native used the real runnerd and the repository's deterministic Codex protocol fixture. Real hosted provider/tool cleanup remains a release acceptance check.
Repository typecheck, production build, Storybook build, token gates, targeted
UI tests, and tree-control route tests passed. The broad UI run passed 5,491
tests with one five-second timeout in an unchanged IssuesList test; rerunning
that file passed all 46 tests. The repository test run encountered 17 failures
in unchanged suites, all reproduced in the original checkout:
server/src/__tests__/workspace-runtime.test.ts: 2 failures.server/src/services/workspace-runtime-exposure.test.ts: 7 failures.server/src/__tests__/execution-workspace-runtime-control-conflict.test.ts: 4 failures.server/src/__tests__/workspace-instance-cleanup.test.ts: 1 failure.server/src/__tests__/company-skills.test.ts: 2 failures.server/src/__tests__/worktree-seed-server-spawn.test.ts: 1 failure.
These baseline failures prevent a green repository-wide test result. The broad run was stopped after more than 30 minutes in its serial server lane once these failures were independently reproduced. Later full-suite groups did not run. The full UI suite and the feature's server route suite were run separately as described above.