From e37180d3e3b5fdb7ded00efcb9321152d9999e9f Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Fri, 1 May 2026 21:00:08 -0400 Subject: [PATCH 1/5] chore(plugin-rpc): raise MAX_RPC_TIMEOUT_MS cap to 60 minutes --- server/src/services/plugin-worker-manager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/services/plugin-worker-manager.ts b/server/src/services/plugin-worker-manager.ts index 8413af10..993a64e5 100644 --- a/server/src/services/plugin-worker-manager.ts +++ b/server/src/services/plugin-worker-manager.ts @@ -57,8 +57,8 @@ import { logger } from "../middleware/logger.js"; /** Default timeout for RPC calls in milliseconds. */ const DEFAULT_RPC_TIMEOUT_MS = 30_000; -/** Hard upper bound for any RPC timeout (5 minutes). Prevents unbounded waits. */ -const MAX_RPC_TIMEOUT_MS = 5 * 60 * 1_000; +/** Hard upper bound for any RPC timeout (60 minutes). Prevents unbounded waits. */ +const MAX_RPC_TIMEOUT_MS = 60 * 60 * 1_000; /** Timeout for the initialize RPC call. */ const INITIALIZE_TIMEOUT_MS = 15_000; -- 2.52.0 From 85cbbc9263e18777cffc748a29e61f8f3822c604 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Sun, 3 May 2026 10:23:54 -0400 Subject: [PATCH 2/5] revert: restore paperclip-dev skill (validation requires it for now) The earlier fix/remove-paperclip-dev-skill removed the bundled skill, but companies have stale company_skills rows that reference it as required, breaking 'Invalid company skill selection' validation. Put the file back to unblock; the underlying force-required-on-bundled bug remains and should be fixed in code rather than by deleting the skill. --- skills/paperclip-dev/SKILL.md | 267 ++++++++++++++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 skills/paperclip-dev/SKILL.md diff --git a/skills/paperclip-dev/SKILL.md b/skills/paperclip-dev/SKILL.md new file mode 100644 index 00000000..d392d327 --- /dev/null +++ b/skills/paperclip-dev/SKILL.md @@ -0,0 +1,267 @@ +--- +name: paperclip-dev +required: false +description: > + Develop and operate a local Paperclip instance — start and stop servers, + pull updates from master, run builds and tests, manage worktrees, back up + databases, and diagnose problems. Use whenever you need to work on the + Paperclip codebase itself or keep a running instance healthy. +--- + +# Paperclip Dev + +This skill covers the day-to-day workflows for developing and operating a local Paperclip instance. It assumes you are working inside the Paperclip repo checkout with `origin` pointing to `git@github.com:paperclipai/paperclip.git`. + +> **OPEN SOURCE HYGIENE:** This repository is public-facing. Treat anything you push to `origin` as publishable. Never commit or push secrets, API keys, tokens, private logs, PII, customer data, or machine-local configuration that should stay private. Keep git history tidy as well: avoid pushing throwaway branches, noisy checkpoint commits, or speculative work that does not need to be shared upstream. + +> **MANDATORY:** Before running any CLI command, building, testing, or managing worktrees, you MUST read `doc/DEVELOPING.md` in the Paperclip repo. It is the canonical reference for all `paperclipai` CLI commands, their options, build/test workflows, database operations, worktree management, and diagnostics. Do NOT guess at flags or options — read the doc first. + +## Quick Command Reference + +These are the most common commands. For full option tables and details, see `doc/DEVELOPING.md`. + +| Task | Command | +|------|---------| +| Start server (first time or normal) | `npx paperclipai run` | +| Dev mode with hot reload | `pnpm dev` | +| Stop dev server | `pnpm dev:stop` | +| Build | `pnpm build` | +| Type-check | `pnpm typecheck` | +| Run tests | `pnpm test` | +| Run migrations | `pnpm db:migrate` | +| Regenerate Drizzle client | `pnpm db:generate` | +| Back up database | `npx paperclipai db:backup` | +| Health check | `npx paperclipai doctor --repair` | +| Print env vars | `npx paperclipai env` | +| Trigger agent heartbeat | `npx paperclipai heartbeat run --agent-id ` | +| Install agent skills locally | `npx paperclipai agent local-cli --company-id ` | + +## Pulling from Master + +```bash +git fetch origin && git pull origin master +pnpm install && pnpm build +``` + +If schema changes landed, also run `pnpm db:generate && pnpm db:migrate`. + +## Worktrees + +Paperclip worktrees combine git worktrees with isolated Paperclip instances — each gets its own database, server port, and environment seeded from the primary instance. + +> **MANDATORY:** Before creating or managing worktrees, you MUST read the "Worktree-local Instances" and "Worktree CLI Reference" sections in `doc/DEVELOPING.md`. That is the canonical reference for all worktree commands, their options, seed modes, and environment variables. + +### When to Use Worktrees + +- Starting a feature branch that needs its own Paperclip environment +- Running parallel agent work without cross-contaminating the primary instance +- Testing Paperclip changes in isolation before merging + +### Command Overview + +The CLI has two tiers (see `doc/DEVELOPING.md` for full option tables): + +| Command | Purpose | +|---------|---------| +| `worktree:make ` | Create worktree + isolated instance in one step | +| `worktree:list` | List worktrees and their Paperclip status | +| `worktree:merge-history` | Preview/import issue history between worktrees | +| `worktree:cleanup ` | Remove worktree, branch, and instance data | +| `worktree init` | Bootstrap instance inside existing worktree | +| `worktree env` | Print shell exports for worktree instance | +| `worktree reseed` | Refresh worktree DB from another instance | +| `worktree repair` | Fix broken/missing worktree instance metadata | + +### Typical Workflow + +```bash +# 1. Create a worktree for a feature +npx paperclipai worktree:make my-feature --start-point origin/main + +# 2. Move into the worktree (path printed by worktree:make) and source the environment +cd +eval "$(npx paperclipai worktree env)" + +# 3. Start the isolated Paperclip server +npx paperclipai run + +# 4. Do your work + +# 5. When done, merge history back if needed +npx paperclipai worktree:merge-history --from paperclip-my-feature --to current --apply + +# 6. Clean up +npx paperclipai worktree:cleanup my-feature +``` + +## Forks — Prefer Pushing to a User Fork + +If the user has a personal fork of `paperclipai/paperclip` configured as a git remote, push your feature branches to **that fork** instead of creating branches on the main repo. This keeps the upstream branch list clean and matches the standard open-source contribution flow. + +### Detect a fork remote + +Before pushing or creating a PR, list remotes and check for one that points at a non-`paperclipai` GitHub fork: + +```bash +git remote -v +``` + +Treat any remote whose URL points to `github.com:/paperclip` (or `github.com//paperclip.git`) as the user's fork. Common names are `fork`, ``, or `myfork`. The remote named `origin` or `upstream` that points at `paperclipai/paperclip` is the canonical upstream — do not push feature branches there if a fork exists. + +### Pushing to the fork + +```bash +# Push the current branch to the user's fork and set upstream +git push -u HEAD +``` + +Then create the PR from the fork branch: + +```bash +gh pr create --repo paperclipai/paperclip --head : ... +``` + +`gh pr create` usually figures out the head ref automatically when run from a branch tracking the fork; the explicit `--head :` form is the reliable fallback when it does not. + +### When no fork exists + +If `git remote -v` shows only `paperclipai/paperclip` remotes (no user fork), fall back to pushing branches to `origin` as before. Do NOT create a fork on the user's behalf — ask first. + +### Keeping the fork up to date + +The canonical remote that points at `paperclipai/paperclip` may be named `origin` **or** `upstream` depending on how the user set up the repo. Detect it the same way as in the "Detect a fork remote" step, then fetch and push from/with that remote so the sync works under either convention: + +```bash +UPSTREAM_REMOTE=$(git remote -v | awk '/paperclipai\/paperclip.*\(fetch\)/{print $1; exit}') +git fetch "$UPSTREAM_REMOTE" +git push "${UPSTREAM_REMOTE}/master:master" +``` + +## Pull Requests + +> **MANDATORY PRE-FLIGHT:** Before creating ANY pull request, you MUST read the canonical source files listed below. Do NOT run `gh pr create` until you have read these files and verified your PR body matches every required section. + +### Step 1 — Read the canonical files + +You MUST read all three of these files before creating a PR: + +1. **`.github/PULL_REQUEST_TEMPLATE.md`** — the required PR body structure +2. **`CONTRIBUTING.md`** — contribution conventions, PR requirements, and thinking-path examples +3. **`.github/workflows/pr.yml`** — CI checks that gate merge + +### Step 2 — Validate your PR body against this checklist + +After reading the template, verify your `--body` includes every one of these sections (names must match exactly): + +- [ ] `## Thinking Path` — blockquote style, 5-8 reasoning steps +- [ ] `## What Changed` — bullet list of concrete changes +- [ ] `## Verification` — how a reviewer confirms this works +- [ ] `## Risks` — what could go wrong +- [ ] `## Model Used` — provider, model ID, version, capabilities +- [ ] `## Checklist` — copied from the template, items checked off + +If any section is missing or empty, do NOT submit the PR. Go back and fill it in. + +### Step 3 — Create the PR + +Only after completing Steps 1 and 2, run `gh pr create`. Use the template contents as the structure for `--body` — do not write a freeform summary. + +## Hard Rules — Do NOT Bypass + +These rules exist because agents have caused real damage by improvising around CLI failures. Follow them exactly. + +1. **CLI is the only interface to worktrees and databases.** All worktree and database operations MUST go through `npx paperclipai` / `pnpm paperclipai` commands. You MUST NOT: + - Run `pg_dump`, `pg_restore`, `psql`, `createdb`, `dropdb`, or any raw postgres commands + - Manually set `DATABASE_URL` to point a worktree server at another instance's database + - Run `rm -rf` on any `.paperclip/`, `.paperclip-worktrees/`, or `db/` directory + - Directly manipulate embedded postgres data directories + - Kill postgres processes by PID + +2. **If a CLI command fails, stop and report.** Do NOT attempt workarounds. If `worktree:make`, `worktree reseed`, `worktree init`, `worktree:cleanup`, or any other `paperclipai` command fails: + - Report the exact error message in your task comment + - Set the task to `blocked` + - Suggest running `npx paperclipai doctor --repair` or recreating the worktree from scratch + - Do NOT try to manually replicate what the CLI does + +3. **Never share databases between instances.** Each worktree instance gets its own isolated database. Never override `DATABASE_URL` to point one instance at another's database. This destroys isolation and can corrupt production data. + +4. **Starting a dev server in a worktree requires setup first.** The correct sequence is: + ```bash + # If the worktree already exists but has no running instance: + cd + eval "$(npx paperclipai worktree env)" + pnpm install && pnpm build + npx paperclipai run # or pnpm dev + + # If the worktree needs a fresh database: + npx paperclipai worktree reseed --seed-mode full + + # If the worktree is broken beyond repair: + npx paperclipai worktree:cleanup + npx paperclipai worktree:make --seed-mode full + ``` + If any step fails, follow rule 2 — stop and report. + +5. **Seeding is a CLI operation.** When asked to seed a worktree database from the main instance, use `worktree reseed` or recreate with `worktree:make --seed-mode full`. Read `doc/DEVELOPING.md` for the full option tables. Never attempt manual database copying. + +## Persistent Dev Servers (for Manual Testing) + +When an agent needs to start a dev server that outlives the current heartbeat — for example, so a human or QA agent can manually test against it — the server process **must** be launched in a detached session. A process started directly from a heartbeat shell is killed when the heartbeat exits. + +### Use `tmux` for persistent servers + +```bash +# 1. cd into the worktree (or main repo) and source the environment +cd +eval "$(npx paperclipai worktree env)" # skip if using the primary instance + +# 2. Start the dev server in a named, detached tmux session +tmux new-session -d -s 'pnpm dev' + +# Example with a descriptive name: +tmux new-session -d -s auth-fix-3102 'pnpm dev' +``` + +### Managing the session + +| Task | Command | +|------|---------| +| Check if the session is alive | `tmux has-session -t 2>/dev/null && echo running` | +| View server output | `tmux capture-pane -t -p` | +| Kill the session | `tmux kill-session -t ` | +| List all tmux sessions | `tmux list-sessions` | + +### Verifying the server is reachable + +After launching, confirm the port is listening before reporting success: + +```bash +# Wait briefly for startup, then verify +sleep 3 +curl -sf http://127.0.0.1:/api/health && echo "Server is up" +lsof -nP -iTCP: -sTCP:LISTEN +``` + +### Key rules + +1. **Always use `tmux` (or equivalent)** when a dev server needs to stay running after the heartbeat ends. A server started directly from the agent shell will die when the heartbeat exits, even if it appeared healthy moments before. +2. **Name the session descriptively** — include the worktree name and port (e.g., `auth-fix-3102`). +3. **Verify the server is listening** before reporting the URL to anyone. +4. **Do not use `nohup` or `&` alone** — these are unreliable for agent shells that may have their entire process group killed. +5. **Clean up when done** — kill the tmux session when the testing is complete. + +## Common Mistakes + +| Mistake | Fix | +|---------|-----| +| Server won't start | Run `npx paperclipai doctor --repair` to diagnose and auto-fix | +| Forgetting to source worktree env | Run `eval "$(npx paperclipai worktree env)"` after cd-ing into the worktree | +| Stale dependencies after pull | Run `pnpm install && pnpm build` after pulling | +| Schema out of date after pull | Run `pnpm db:generate && pnpm db:migrate` | +| Reseeding while target DB is running | Stop the target server first, or use `--allow-live-target` | +| Cleaning up with unmerged commits | Merge or push first, or use `--force` if intentionally discarding | +| Running agents against wrong instance | Verify `PAPERCLIP_API_URL` points to the correct port | +| CLI command fails | Do NOT work around it — report the error and block (see Hard Rules above) | +| Agent tries manual postgres operations | NEVER do this — all DB ops go through the CLI (see Hard Rules above) | +| Dev server dies between heartbeats | Launch in a detached `tmux` session — see "Persistent Dev Servers" above | +| Pushed feature branch to `paperclipai/paperclip` when a fork exists | Push to the user's fork remote instead — see "Forks" above | -- 2.52.0 From 37e0aac9713f6ce0bafd05fa999d6be276ee75b3 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Sun, 3 May 2026 15:38:18 -0400 Subject: [PATCH 3/5] ci: build prod image from .farhoodlabs/Dockerfile Pulls the prod image up to the same toolset as the dev image (kubectl, kubeseal, uv/uvx, forgejo CLIs, nano, vim) without diverging the upstream root Dockerfile. Both build-dev.yml and build-prod.yml now share the same fork-overlay Dockerfile; only the image tag and trigger branch differ. --- .farhoodlabs/.github/workflows/build-prod.yml | 1 + .github/workflows/build-prod.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.farhoodlabs/.github/workflows/build-prod.yml b/.farhoodlabs/.github/workflows/build-prod.yml index 8e187759..468a0041 100644 --- a/.farhoodlabs/.github/workflows/build-prod.yml +++ b/.farhoodlabs/.github/workflows/build-prod.yml @@ -47,6 +47,7 @@ jobs: uses: docker/build-push-action@v6 with: context: . + file: .farhoodlabs/Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/build-prod.yml b/.github/workflows/build-prod.yml index 8e187759..468a0041 100644 --- a/.github/workflows/build-prod.yml +++ b/.github/workflows/build-prod.yml @@ -47,6 +47,7 @@ jobs: uses: docker/build-push-action@v6 with: context: . + file: .farhoodlabs/Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} -- 2.52.0 From 417782a6ec6b1d2adb494a29259066c9f55db1e6 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Tue, 12 May 2026 07:33:10 -0400 Subject: [PATCH 4/5] feat(plugin-sdk): thread agentId into environmentAcquireLease params Add an optional agentId field to PluginEnvironmentAcquireLeaseParams and thread it through the host's environment-runtime + run-orchestrator call sites so plugin-backed sandbox providers can scope lease state (subdirs, PVCs, etc.) per agent without an SDK callback or DB lookup. The field is required-but-nullable on the internal EnvironmentDriverAcquireInput (string | null) so every call site has to think about whether it has an agent context. Ad-hoc operator probes (agent test-environment route) pass null. The plugin RPC payload omits the field entirely when null, keeping wire compatibility with older plugin worker SDKs. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/plugins/sdk/src/protocol.ts | 7 +++++++ .../heartbeat-plugin-environment.test.ts | 2 ++ server/src/routes/agents.ts | 1 + .../src/services/environment-run-orchestrator.ts | 2 ++ server/src/services/environment-runtime.ts | 16 ++++++++++++++++ 5 files changed, 28 insertions(+) diff --git a/packages/plugins/sdk/src/protocol.ts b/packages/plugins/sdk/src/protocol.ts index 1c849cac..f3c05a5e 100644 --- a/packages/plugins/sdk/src/protocol.ts +++ b/packages/plugins/sdk/src/protocol.ts @@ -379,6 +379,13 @@ export interface PluginEnvironmentLease { export interface PluginEnvironmentAcquireLeaseParams extends PluginEnvironmentDriverBaseParams { runId: string; + /** + * UUID of the agent the run is being acquired for. Omitted only for ad-hoc + * invocations (e.g. operator-initiated environment test probes) where no + * agent context exists. Plugins should treat undefined as "no per-agent + * partitioning available" and fall back to environment-level behavior. + */ + agentId?: string; workspaceMode?: string; requestedCwd?: string; } diff --git a/server/src/__tests__/heartbeat-plugin-environment.test.ts b/server/src/__tests__/heartbeat-plugin-environment.test.ts index 381f4556..28742781 100644 --- a/server/src/__tests__/heartbeat-plugin-environment.test.ts +++ b/server/src/__tests__/heartbeat-plugin-environment.test.ts @@ -209,6 +209,7 @@ describeEmbeddedPostgres("heartbeat plugin environments", () => { issueId: null, config: { template: "base" }, runId: run!.id, + agentId, workspaceMode: "shared_workspace", }); await vi.waitFor(() => { @@ -426,6 +427,7 @@ describeEmbeddedPostgres("heartbeat plugin environments", () => { issueId, config: { template: "new" }, runId: run!.id, + agentId, workspaceMode: "shared_workspace", }); }, 15_000); diff --git a/server/src/routes/agents.ts b/server/src/routes/agents.ts index d1b6d78c..0567bb80 100644 --- a/server/src/routes/agents.ts +++ b/server/src/routes/agents.ts @@ -319,6 +319,7 @@ export function agentRoutes( companyId: input.companyId, environment, issueId: null, + agentId: null, heartbeatRunId: null, persistedExecutionWorkspace: null, }); diff --git a/server/src/services/environment-run-orchestrator.ts b/server/src/services/environment-run-orchestrator.ts index 9b0a2066..5014a092 100644 --- a/server/src/services/environment-run-orchestrator.ts +++ b/server/src/services/environment-run-orchestrator.ts @@ -206,6 +206,7 @@ export function environmentRunOrchestrator( companyId: string; environment: Environment; issueId: string | null; + agentId: string; heartbeatRunId: string; persistedExecutionWorkspace: Pick | null; }): Promise { @@ -280,6 +281,7 @@ export function environmentRunOrchestrator( companyId: input.companyId, environment, issueId: input.issueId, + agentId: input.agentId, heartbeatRunId: input.heartbeatRunId, persistedExecutionWorkspace: input.persistedExecutionWorkspace, }); diff --git a/server/src/services/environment-runtime.ts b/server/src/services/environment-runtime.ts index d876b6d3..1cc6e7b4 100644 --- a/server/src/services/environment-runtime.ts +++ b/server/src/services/environment-runtime.ts @@ -103,6 +103,14 @@ export interface EnvironmentDriverAcquireInput { companyId: string; environment: Environment; issueId: string | null; + /** + * UUID of the owning agent. Null for ad-hoc invocations (e.g. + * operator-initiated `Test` probes) that are not tied to a specific agent. + * Threaded through to plugin-backed sandbox providers so they can scope + * lease state (PVCs, subdirs, etc.) per-agent without needing to look it + * up via callback. + */ + agentId: string | null; /** * UUID of the owning heartbeat run, or null for ad-hoc invocations * (e.g. operator-initiated `Test` probes) that are not tied to a run. @@ -489,6 +497,7 @@ function createSandboxEnvironmentDriver( // UUID so providers that validate or persist the runId still see // a well-formed identifier. runId: input.heartbeatRunId ?? randomUUID(), + ...(input.agentId ? { agentId: input.agentId } : {}), workspaceMode: input.executionWorkspaceMode ?? undefined, }, resolvePluginSandboxRpcTimeoutMs(workerConfig), @@ -897,6 +906,7 @@ function createPluginEnvironmentDriver( issueId: input.issueId, config: parsed.config.driverConfig, runId: input.heartbeatRunId ?? randomUUID(), + ...(input.agentId ? { agentId: input.agentId } : {}), workspaceMode: input.executionWorkspaceMode ?? undefined, }); @@ -1110,6 +1120,11 @@ export function environmentRuntimeService( companyId: string; environment: Environment; issueId: string | null; + /** + * UUID of the owning agent. Null for ad-hoc invocations (e.g. + * operator-initiated `Test` probes). + */ + agentId: string | null; /** Null for ad-hoc invocations (e.g. operator-initiated `Test` probes). */ heartbeatRunId: string | null; persistedExecutionWorkspace: Pick | null; @@ -1126,6 +1141,7 @@ export function environmentRuntimeService( companyId: input.companyId, environment: input.environment, issueId: input.issueId, + agentId: input.agentId, heartbeatRunId: input.heartbeatRunId, executionWorkspaceId: leaseContext.executionWorkspaceId, executionWorkspaceMode: leaseContext.executionWorkspaceMode, -- 2.52.0 From 73f46857294aa2f0a7cb2fa5baf4aa8b43d680f7 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Tue, 12 May 2026 07:36:08 -0400 Subject: [PATCH 5/5] feat(plugin-sdk): also thread agentId into environmentResumeLease params Symmetric with the acquireLease change. Lets plugin-backed sandbox providers reject a reusable lease whose stored agentId doesn't match the current run's agent, forcing the host to acquire a fresh lease instead of stomping the previous agent's workspace state. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/plugins/sdk/src/protocol.ts | 8 ++++++++ server/src/services/environment-runtime.ts | 1 + 2 files changed, 9 insertions(+) diff --git a/packages/plugins/sdk/src/protocol.ts b/packages/plugins/sdk/src/protocol.ts index f3c05a5e..8cf74f68 100644 --- a/packages/plugins/sdk/src/protocol.ts +++ b/packages/plugins/sdk/src/protocol.ts @@ -393,6 +393,14 @@ export interface PluginEnvironmentAcquireLeaseParams extends PluginEnvironmentDr export interface PluginEnvironmentResumeLeaseParams extends PluginEnvironmentDriverBaseParams { providerLeaseId: string; leaseMetadata?: Record; + /** + * UUID of the agent the run is being resumed for. Symmetric with + * `PluginEnvironmentAcquireLeaseParams.agentId`. Plugins can compare this + * to the agentId they stored in `leaseMetadata` at acquire time; if it + * doesn't match, return `{ providerLeaseId: null, metadata: { expired: true } }` + * to force the host to create a fresh lease for the current agent. + */ + agentId?: string; } export interface PluginEnvironmentReleaseLeaseParams extends PluginEnvironmentDriverBaseParams { diff --git a/server/src/services/environment-runtime.ts b/server/src/services/environment-runtime.ts index 1cc6e7b4..82a16221 100644 --- a/server/src/services/environment-runtime.ts +++ b/server/src/services/environment-runtime.ts @@ -476,6 +476,7 @@ function createSandboxEnvironmentDriver( config: workerConfig, providerLeaseId: reusableLease.providerLeaseId, leaseMetadata: reusableLease.metadata ?? undefined, + ...(input.agentId ? { agentId: input.agentId } : {}), }, resolvePluginSandboxRpcTimeoutMs(workerConfig), ).then((resumed) => -- 2.52.0