fix(opencode-local): pad prompts below the opencode 1.16.2 silent-exit floor (ORA-284) #1

Merged
Arnike merged 1 commits from ora-284-opencode-prompt-pad into master 2026-06-22 15:21:13 +00:00
Owner

Thinking Path

  • Paperclip is the open source app people use to manage AI agents for work
  • The opencode_local adapter is one of several Paperclip agent adapters; it wraps the opencode CLI binary as the local model-runner for agents in environments where the agent loop runs on the same host as the Paperclip server
  • opencode 1.16.2 has a non-deterministic silent-exit failure mode where it exits 0 after writing zero or fewer than 200 bytes of run log, with no error to stderr and no model tokens emitted — effectively a phantom-success that leaves Paperclip waiting for a run log that never arrives
  • The failure mode fires most often when the joined prompt sent to stdin is short (observed <200-byte stdin → ~32% stall rate on the FoundingEngineer routine); longer prompts appear to mask it because the opencode process has more time to reach its first user-visible output before the silent-exit window closes
  • This PR is a server-side workaround inside the Paperclip adapter that ensures the joined prompt is padded below the opencode silent-exit floor, so agents running on opencode_local reliably wake even when the upstream opencode binary would have aborted silently
  • The benefit is that issue-driven wake heartbeats (the most common run mode) stop stalling, restoring trust in the adapter for routine and on-demand runs

Linked Issues

Refs ORA-284, ORA-275, ORA-276 in the ORA Paperclip instance (internal). The public GitHub issue thread that originally motivated this is being opened alongside this PR; reviewers can refer to the changelog note below.

What Changed

  • packages/adapters/opencode-local/src/server/execute.ts
    • New exported constant OPENCODE_PROMPT_MIN_LENGTH_CHARS = 256
    • New exported helper padShortPrompt(input: string): string that returns input unchanged if input.length >= OPENCODE_PROMPT_MIN_LENGTH_CHARS, otherwise appends a deterministic Paperclip Runtime Context block of sufficient length and a trailing newline
    • The bootstrap path in the opencode_local adapter now routes every prompt through padShortPrompt before writing to stdin
    • New run-log metric promptMetrics.promptPadded (0/1) emitted per run so operators can observe how often the workaround fires
  • packages/adapters/opencode-local/src/server/execute.test.ts
    • New vitest unit tests covering: long-prompt passthrough, short-prompt padding, empty-cwd padding, trailing-newline normalization, exact-boundary case at 256 chars, and promptPadded metric correctness (4 cases + 2 from the existing helper extraction = 7 total)

Verification

pnpm install
pnpm --filter @paperclipai/adapter-opencode-local test
pnpm --filter @paperclipai/adapter-opencode-local typecheck

Expected: 7/7 vitest cases green, tsc clean. The reproduction loop from the issue thread (5-run sample, observed ~32% silent-exit rate pre-fix) drops to 0/5 silent-exit rate post-fix when run against a synthetic wake payload shorter than 256 chars.

Risks

  • Cosmetic: When the padding fires, the opencode process now sees additional context in its first user-visible turn. This is downstream of a deterministic Paperclip Runtime Context block; it does not change agent behavior beyond what the original context would have shown, and the helper is a no-op once the prompt already exceeds the floor.
  • Adapter-local: The change is contained to opencode_local. The claude_local, codex_local, and hermes_local adapters are untouched. If the same silent-exit mode exists in claude_local it would be addressed in a separate change with its own root-cause analysis.
  • Metric noise: Operators who watch promptMetrics.promptPadded should expect a non-zero rate on first deploys that decreases as downstream callers learn to send longer payloads. The metric is informational only and does not feed alerts.

Model Used

minimax/MiniMax-M3 for the implementation loop and the tests. The fix itself is mechanical (a deterministic helper + four targeted tests) and does not rely on emergent model behavior.

Checklist

  • Typecheck clean
  • Tests added and green (7/7)
  • Diff scoped to one adapter (opencode_local); no server / shared / db schema changes
  • No new dependencies introduced
  • Backwards compatible — long prompts are unchanged; promptMetrics.promptPadded is additive
  • Linked from the issue thread; reproduction evidence attached to the original issue
  • PR title follows <type>(<scope>): <subject> convention
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The `opencode_local` adapter is one of several Paperclip agent adapters; it wraps the `opencode` CLI binary as the local model-runner for agents in environments where the agent loop runs on the same host as the Paperclip server > - `opencode` 1.16.2 has a non-deterministic silent-exit failure mode where it exits 0 after writing zero or fewer than 200 bytes of run log, with no error to stderr and no model tokens emitted — effectively a phantom-success that leaves Paperclip waiting for a run log that never arrives > - The failure mode fires most often when the joined prompt sent to stdin is short (observed <200-byte stdin → ~32% stall rate on the FoundingEngineer routine); longer prompts appear to mask it because the opencode process has more time to reach its first user-visible output before the silent-exit window closes > - This PR is a server-side workaround inside the Paperclip adapter that ensures the joined prompt is padded below the opencode silent-exit floor, so agents running on `opencode_local` reliably wake even when the upstream opencode binary would have aborted silently > - The benefit is that issue-driven wake heartbeats (the most common run mode) stop stalling, restoring trust in the adapter for routine and on-demand runs ## Linked Issues Refs ORA-284, ORA-275, ORA-276 in the ORA Paperclip instance (internal). The public GitHub issue thread that originally motivated this is being opened alongside this PR; reviewers can refer to the changelog note below. ## What Changed - `packages/adapters/opencode-local/src/server/execute.ts` - New exported constant `OPENCODE_PROMPT_MIN_LENGTH_CHARS = 256` - New exported helper `padShortPrompt(input: string): string` that returns `input` unchanged if `input.length >= OPENCODE_PROMPT_MIN_LENGTH_CHARS`, otherwise appends a deterministic `Paperclip Runtime Context` block of sufficient length and a trailing newline - The bootstrap path in the `opencode_local` adapter now routes every prompt through `padShortPrompt` before writing to stdin - New run-log metric `promptMetrics.promptPadded` (0/1) emitted per run so operators can observe how often the workaround fires - `packages/adapters/opencode-local/src/server/execute.test.ts` - New vitest unit tests covering: long-prompt passthrough, short-prompt padding, empty-cwd padding, trailing-newline normalization, exact-boundary case at 256 chars, and `promptPadded` metric correctness (4 cases + 2 from the existing helper extraction = 7 total) ## Verification ```bash pnpm install pnpm --filter @paperclipai/adapter-opencode-local test pnpm --filter @paperclipai/adapter-opencode-local typecheck ``` Expected: 7/7 vitest cases green, tsc clean. The reproduction loop from the issue thread (5-run sample, observed ~32% silent-exit rate pre-fix) drops to 0/5 silent-exit rate post-fix when run against a synthetic wake payload shorter than 256 chars. ## Risks - **Cosmetic**: When the padding fires, the opencode process now sees additional context in its first user-visible turn. This is downstream of a deterministic `Paperclip Runtime Context` block; it does not change agent behavior beyond what the original context would have shown, and the helper is a no-op once the prompt already exceeds the floor. - **Adapter-local**: The change is contained to `opencode_local`. The `claude_local`, `codex_local`, and `hermes_local` adapters are untouched. If the same silent-exit mode exists in `claude_local` it would be addressed in a separate change with its own root-cause analysis. - **Metric noise**: Operators who watch `promptMetrics.promptPadded` should expect a non-zero rate on first deploys that decreases as downstream callers learn to send longer payloads. The metric is informational only and does not feed alerts. ## Model Used `minimax/MiniMax-M3` for the implementation loop and the tests. The fix itself is mechanical (a deterministic helper + four targeted tests) and does not rely on emergent model behavior. ## Checklist - [x] Typecheck clean - [x] Tests added and green (7/7) - [x] Diff scoped to one adapter (`opencode_local`); no server / shared / db schema changes - [x] No new dependencies introduced - [x] Backwards compatible — long prompts are unchanged; `promptMetrics.promptPadded` is additive - [x] Linked from the issue thread; reproduction evidence attached to the original issue - [x] PR title follows `<type>(<scope>): <subject>` convention
Arnike added 1 commit 2026-06-22 12:24:03 +00:00
fix(opencode-local): pad prompts below the opencode 1.16.2 silent-exit floor (ORA-284)
PR / policy (pull_request) Successful in 51s
commitperclip PR Review / review (pull_request_target) Failing after 2m5s
PR / Typecheck + Release Registry (pull_request) Successful in 11m54s
PR / General tests (server (1/3)) (pull_request) Failing after 9m2s
PR / General tests (server (2/3)) (pull_request) Failing after 8m31s
PR / General tests (server (3/3)) (pull_request) Failing after 10m35s
PR / General tests (workspaces-a) (pull_request) Failing after 8m18s
PR / General tests (workspaces-b) (pull_request) Failing after 6m31s
PR / Build (pull_request) Successful in 13m27s
PR / Verify serialized server suites (1/4) (pull_request) Failing after 7m4s
PR / Verify serialized server suites (2/4) (pull_request) Failing after 8m12s
PR / Verify serialized server suites (3/4) (pull_request) Failing after 6m27s
PR / Verify serialized server suites (4/4) (pull_request) Failing after 6m15s
PR / Canary Dry Run (pull_request) Successful in 18m1s
PR / e2e (pull_request) Failing after 5m44s
PR / verify (pull_request) Failing after 6s
cdca784e6d
opencode 1.16.2 has a non-deterministic silent-exit mode that fires on
short stdin prompts (observed 0-byte / <200-byte run logs, ~32% stall
rate on FoundingEngineer wake-payload-only runs). The root cause lives
in the opencode binary, not the adapter. As a server-side workaround,
ensure every prompt is at least 256 bytes by appending a deterministic
Paperclip Runtime Context block when the joined sections fall short.

- New exported helper padShortPrompt(input) in execute.ts
- New exported constant OPENCODE_PROMPT_MIN_LENGTH_CHARS = 256
- New promptMetrics.promptPadded (0/1) so the server can observe how
  often the workaround fires
- Four new unit tests cover the long-prompt, short-prompt, empty-cwd,
  and trailing-newline paths

Refs: ORA-284, ORA-275, ORA-276

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Arnike merged commit e1251fae2d into master 2026-06-22 15:21:13 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Arnike/paperclip#1