## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - Idle sleep must wait for accepted work and durable cleanup.
> - The retry dispatcher keeps its due date after it promotes a retry.
> - Finished retries and passive local directories can therefore look
like pending work.
> - This pull request requires completion proof and permits known
passive directory records.
> - It retains the records and keeps real work and unknown state awake.
## Linked Issues or Issue Description
Related: #15897 and #15851. The open runtime-service work in #13413
retains its separate service and ingress guarantees. This patch does not
change those checks.
**What happened?**
Idle sleep treats a consumed retry date as pending work. It also blocks
on every retained execution workspace, including a local directory
without runtime activity.
**Steps to reproduce**
1. Promote a scheduled retry, then finish its run without accounting
debt. Retain its scheduled retry date.
2. Retain a local primary directory or Git worktree without runtime
configuration, commands, or cleanup due.
3. Request an owned idle safety report. The report returns `present` for
this history.
**Paperclip version or commit**
Reproduced on master at 4b59e2a2ec.
**Deployment mode**
Server with PostgreSQL. The regression tests use embedded PostgreSQL.
## What Changed
- Permit terminal runs with a finish time at or after their retry date
and no accounting debt. Apply that proof to linked wake,
answer-delivery, and verified failed-lease history.
- Permit known passive local primary directories and Git worktrees. Keep
due cleanup, commands, runtime configuration, shared project runtime
inheritance, pending reopen, runtime owners, and unfamiliar state awake.
- Retain retry dates, workspace records, files, and metadata. Document
the proof and retention limits.
## Verification
- Passed all 200 tests in `pnpm exec vitest run
server/src/__tests__/idle-sleep-safety.test.ts` against embedded
PostgreSQL.
- Passed the 52 retry dispatcher tests.
- Passed `pnpm -r typecheck` and `pnpm build`. After the provider
correction, server typecheck and build passed again.
- Full CI is green for commit
`0510669439e855b90a4e03a2fc7827503cea507f`, including all test shards,
typecheck, build, and browser end-to-end tests. One browser test
initially raced its comment write; the failed jobs passed on retry
without a source change.
- Local `pnpm test:run` recorded 18,721 passed, 105 skipped, and four
failures in existing AI-login, Git-fixture, and native runner-recovery
tests; it exited before the remaining workspace and serialized groups.
The AI-login test passed alone. Both Git fixtures passed with
command-scoped `init.defaultBranch=master`. The native recovery failure
also occurs on the parent revision on this Mac. These suites pass in CI.
- Greptile Apex scored the final commit 5/5. Its provider-coverage
comment was addressed and the review thread resolved.
- Reviewed the diff for secrets, private data, generated files, and
unrelated changes.
## Risks
- Missing finish times, later retry dates, active children, and
accounting debt continue to block sleep. Failed lease history still
requires an exact provider termination receipt.
- Future cleanup eligibility can wait for the next wake. Due cleanup
blocks sleep. Storage must remain attached across sleep and keep its
backup policy.
- Shared directories with a project workspace and unfamiliar providers,
strategies, modes, or configuration remain conservative blockers.
- No migration, provider teardown, retry scheduling change, or tenant
data deletion.
## Model Used
OpenAI Codex, GPT-6, with repository tools and code execution. The
runtime does not expose a more specific model snapshot 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
- [ ] I have run tests locally and they pass (focused tests pass; the
full local run has the environmental failures documented above)
- [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>
12 KiB
Automatic idle sleep safety
GET /api/instance/task-drain?idleSleepSafety=1&ownerId=<owner> adds an
instance-wide idleSleepSafety report for instance administrators and signed
Cloud control callers. Ordinary task drains keep their existing behavior.
Company members and agents cannot read this report.
Hosting protocol
- Hold external admission at the hosting layer. Queue user requests for wake or abort sleep when activity arrives. Do not send traffic into an instance that is being stopped.
- Send
POST /api/instance/task-drainwith{ "purpose": "idle", "ttlMs": 120000 }. Idle holds require a TTL from 5 to 300 seconds. Save the returnedownerIdandexpiresAt. An existing drain returns 409. An unsupported purpose must leave automatic sleep disabled. - Poll the report with that exact owner. Only
{ "version": 1, "backgroundWork": "none" }permits sleep. Require the sameownerId,draining: true, zero run/wake counters, and enough remaining TTL for the provider stop.present,unknown, missing fields, or errors must keep the instance awake. Do not interpretquiescentalone as authorization. - Revalidate the same hold immediately before stopping the instance. Keep external admission closed through the provider stop. Never use an old report after a restart, expired hold, changed owner, or failed validation.
- If sleep is abandoned, send
DELETE /api/instance/task-drain?ownerId=<owner>. A missing or stale owner cannot release another controller's idle hold. The TTL clears an abandoned hold if the controller loses its response.
An idle hold rejects new tenant HTTP requests and WebSocket upgrades with 503
and Retry-After: 1. Health checks and authenticated task-drain control remain
available. Ordinary deploy drains do not enable this HTTP gate. The host must
handle retry/wake at its ingress before enabling automatic sleep.
Health and control mutations remain tracked through completion, including
signed bootstrap writes. Only the task-drain read skips request tracking.
Its authentication finishes under a separate work token before entering the
report, so it counts concurrent writes without blocking on its own scan.
Retry history
Retry promotion keeps scheduled_retry_at on a run for inspection. That date
is consumed history when the run is terminal, has a finish time at or after the
retry date, and has no accounting work pending. The idle report uses the same
proof for failed ephemeral leases, coalesced wakes, and acknowledged question
responses. A live retry child still blocks through the instance-wide run check.
Unfinished runs, retry dates after the finish time, and pending accounting stay
awake. The inspection does not clear dates or change retry scheduling.
Retained local workspaces
Local directories for primary projects and Git worktrees do not require an awake process by themselves. The report permits their retained rows only in known modes and states, with no runtime configuration, service state, commands, pending reopen, or cleanup already eligible. Saved environment references alone are passive. Shared workspaces with a project workspace remain blockers because they can inherit runtime configuration. Unfamiliar providers, strategies, configuration and malformed metadata also remain blockers.
This does not delete files, alter retention, or clear workspace records. Future cleanup eligibility is an earliest allowed time; eligible cleanup can wait for the next wake. Due cleanup, unfinished operations, runtime leases and runtime services still keep the instance awake through their separate checks. The host must keep workspace storage attached across sleep and preserve its backup policy.
Accepted work and cleanup
HTTP admission is installed before body parsing, authentication, webhooks and
MCP ingress. The Express adapter tracks handler promises in nested routers and
error middleware. Sending a response or losing the client does not complete an
async handler. A callback-only handler that abandons its response without ending it remains a
sleep blocker. An async route that settles after a disconnect releases its
request token; normal cancellation does not permanently block sleep. New routes must be registered before this adapter is installed.
Fire-and-forget work must have durable queue state or use trackIdleWork; a
response is never a substitute for tracking that work.
Accepted live-event WebSocket authentication remains counted until its promise
settles, including after the raw socket closes. Detached startup reconciliation
for built-in agents, managed homes and persisted runtime services also remains
counted after readiness until its writes settle.
Scheduler work already in flight remains counted until its promise settles. Idle holds pause new scheduler admissions. Database backup promises remain counted through success or failure. Configured periodic backups block sleep by default. The opt-in checkpoint mode below preserves a final backup instead of running the timer while asleep. A generation counter invalidates a scan if tracked work both starts and finishes during inspection. Reports remain unknown until startup recovery and HTTP tracking are installed.
Backup checkpoints for idle instances
PAPERCLIP_DB_BACKUP_IDLE_CHECKPOINT_ENABLED=1 opts an instance with automatic
backups enabled into this mode. Keep its database and persistent backup volume
attached across sleep. All writers must use the instance's admission protocol;
this mode cannot cover independent writers to the same database.
On managed instances, checkpoint-enabled safety reads require a verified signed
Cloud control actor. Tenant instance-admin elevation does not grant platform
backup authority. Self-hosted instance administrators retain operator access.
Each owned safety read first checks local work, plugin drains and durable work.
Known local blockers return before opening a database transaction. If every
other check passes, the server takes a fresh backup with the existing backup
engine and the retention policy from Instance Settings. It reads the full gzip
archive, checks its trailer and nonempty content, and syncs the archive and
directory before returning none. The final pre-stop revalidation takes another
fresh backup. Reads can therefore take longer; the host must bound its request
and still leave enough lease time for a confirmed provider stop.
There is no cached backup exemption. Failed, concurrent or untracked backups, other work during the scan, changed owners and expired holds cannot authorize sleep. Client timeouts do not clear the backup's in-flight token. Slow backups may retain an awake instance until a later sleep attempt.
A durable .idle-backup-wake-required marker is written before the dump.
No recurring duplicate archives are produced while the instance is asleep.
The last pre-sleep archive and existing history stay on persistent storage;
no retention pruning runs during sleep. On restart, the server attempts a fresh
backup before enabling idle eligibility, then resumes the normal interval.
Only a verified new backup clears the marker. Failure keeps it for the next
scheduled retry. Disabling automatic backups explicitly still disables this
startup backup. Disabling only checkpoint mode restores the sleep blocker and
continues to honor an existing restart marker.
This does not replace filesystem backups, encryption-key recovery or off-provider disaster recovery. Validate restoration of a staging checkpoint before enabling the flag on customer instances.
The report inspects accounting and sandbox-cleanup spools directly. Any entry, including malformed JSON, temporary files and failed write probes, blocks sleep. Read errors and invalid spool paths return unknown. Recovery readers that skip bad records cannot certify an empty spool.
An orphan cleanup token starts before its spool write and remains held through buffering, queue splices, flush retries and buffer overflow. It ends only after the database owns cleanup. Queued usage receipts stay counted until capture settles. If receipt capture and the database failure fence both fail, a token continues to block sleep until the failure fence is durably written. A query that updates zero rows does not clear that debt. Unrecoverable cleanup requires operator reconciliation; this API does not discard it to save compute.
Persisted work and limits
The report checks all companies in one bounded read-only database transaction. Queued and orphaned runs, timers, retries, accounting debt, unfinished issues, cleanup, active routines, integrations, external ingress credentials, and plugin work block sleep. Completed ordinary run history does not. Less common work sources conservatively block on any retained records. Active issue watchdogs block sleep even when their watched issue is complete and no review has started. Missing migrations, malformed configuration and database errors produce unknown without exposing SQL or tenant data.
Active runtime_auth connections with purpose ai are saved credentials, not
independent workers. Their saved configuration does not block sleep. Login
sessions and runs still pass the existing durable checks. Detached subscription
observations and quota reads remain counted through their actual completion,
including token refresh after a quota response times out. Credential-recovery
journals, partial writes and probes block sleep across restart. Empty company
directories left after successful recovery do not; invalid paths and read
failures remain unknown. Credentials and connection records are retained.
MCP, chat and other connections still block until they have a wake contract.
A failed ephemeral lease is history only with successful cleanup, a release timestamp, and an exact provider termination receipt bound to its company, run, lease and resource. Its run must be finished without retry or accounting debt. Pending cleanup claims and export/stop-and-retain intents still block. Failure labels or cleanup success without that proof do not authorize sleep. Lease history is retained; this check performs no provider teardown or data updates.
Finished login-session history permits sleep only after its promotion claim is
cleared and any retained provider reference has a matching released, expired,
or verified failed ephemeral lease with successful cleanup and a release
timestamp in the same company and environment. The failed lease must satisfy
the same termination and run checks above. Missing cleanup evidence,
cleanup_pending, and live login flows
remain blockers. A finished coalesced wake is history only when its exact
company/agent's linked run has finished without retry or accounting debt.
Image-setup history still blocks: promotion can succeed before teardown is
confirmed, so a terminal label does not prove that its provider resource is gone.
Completed question-answer deliveries permit sleep only after acknowledgement and completion of their exact target run in the same company, without retry or accounting debt. A fallback without a target run requires its exact question's finished durable wake receipt in the same company. A coalesced fallback also requires its exact company/agent's linked run to finish without retry or accounting debt. The question-history index covers every wake status. Pending deliveries, missing completion evidence, and unfinished handoffs still block. Failed deliveries without a pending target are history; other work checks still apply.
Every enabled plugin blocks sleep. A version label does not prove that arbitrary worker code has no background activity. No plugin approval or configuration bypass is supported. Scheduled and externally triggered work needs a durable host wake mechanism before this restriction can be relaxed.
This is a single-process admission protocol. A restart clears the owner. The host must serialize sleep with deploy/wake operations and bind its final check to the instance it stops. An unexpected restart after the final check can still race an external provider stop; a process-local report cannot fence that remote operation by itself. Multiple replicas require shared fencing. The protocol does not make existing in-memory-only debt survive an unplanned process crash.
Related public PR #13413 adds runtime-service controller requirements and uses
the same purpose: "idle" / ownerId vocabulary. This report conservatively
blocks all retained runtime-service state instead of approving such services.