<h3>Greptile Summary</h3>
This PR makes two targeted fixes: (1) the OpenClaw gateway adapter now moves Paperclip context out of a root paperclip key (rejected by OpenClaw's additionalProperties: false schema) into extraSystemPrompt, and falls back to ws://127.0.0.1:18789 when no URL is configured; (2) the claude-local hello probe now classifies a Claude CLI exit-1 with valid rate-limit stream-JSON as a warning rather than a hard failure, with a new exported isClaudeRateLimitedOutput helper and matching tests.
The PR description does not follow the required PR template from CONTRIBUTING.md. Please add the missing sections before merging:
- Thinking Path — trace from Paperclip's purpose down to this specific change
- What Changed — formal bullet list
- Verification — how to confirm it works (the test plan is a good start but should be in this section)
- Risks — behavioral shifts worth calling out (e.g. gateway URL-missing no longer fails fast)
- Model Used — provider, exact model ID/version, and capability details (not just "Made with Cursor")
- Checklist
<h3>Confidence Score: 4/5</h3>
Code changes are correct and well-tested, but the required PR template sections are absent and should be filled in before merge.
All three inline findings are P2 (false-positive risk in raw stdout scan, flaky exit-code assertion in the loopback test, and unused resolveClaimedApiKeyPath). However, the missing PR template (Thinking Path, Risks, Model Used, Checklist) is a stated merge blocker per CONTRIBUTING.md, keeping the score at 4 until that is addressed.
packages/adapters/claude-local/src/server/parse.ts (raw stdout scan), packages/adapters/openclaw-gateway/src/server/test.ts (flaky exitCode assertion), packages/adapters/openclaw-gateway/src/server/execute.ts (unused helper).
<h3>Important Files Changed</h3>
| Filename | Overview |
|----------|----------|
| packages/adapters/claude-local/src/server/parse.ts | Adds isClaudeRateLimitedOutput — logic is sound but the raw stdout scan can false-positive on assistant text containing limit-related phrases. |
| packages/adapters/claude-local/src/server/test.ts | Hello probe updated to treat exit 1 + valid stream-JSON as a success path when rate-limited; logic and fallback detail handling are correct. |
| packages/adapters/openclaw-gateway/src/server/execute.ts | Moves Paperclip context from root paperclip key to extraSystemPrompt; adds default loopback URL; resolveClaimedApiKeyPath is defined but unused (hardcoded path in buildWakeText). |
| packages/adapters/openclaw-gateway/src/server/test.ts | URL-missing failure changed to URL-defaulted info; new test asserts exitCode === 1 which will fail if OpenClaw is running on the loopback port in CI. |
| packages/adapters/openclaw-gateway/src/defaults.ts | New file exporting DEFAULT_OPENCLAW_GATEWAY_WS_URL = "ws://127.0.0.1:18789" — clean and correct. |
| packages/adapters/openclaw-gateway/src/ui/build-config.ts | Config builder now explicitly sets default URL, timeoutSec, and waitTimeoutMs; straightforward and correct. |
| server/src/__tests__/openclaw-gateway-adapter.test.ts | Test assertions updated to match new extraSystemPrompt payload shape and URL-defaulting behaviour; changes are aligned with the implementation. |
| server/src/__tests__/heartbeat-comment-wake-batching.test.ts | Heartbeat batching expectations updated to check extraSystemPrompt instead of paperclip root object; correct alignment with schema change. |
| server/src/__tests__/claude-local-parse-rate-limit.test.ts | New unit tests for isClaudeRateLimitedOutput covering the key detection paths; coverage is adequate for the three main cases. |
| server/src/__tests__/claude-local-adapter-environment.test.ts | Integration test using a fake Claude CLI that emits rate-limit stream-JSON and exits 1; correctly validates the new probe classification path. |
| packages/adapters/claude-local/src/server/index.ts | Re-exports isClaudeRateLimitedOutput from parse.ts; no issues. |
| packages/adapters/openclaw-gateway/src/index.ts | Minor export additions consistent with the rest of the adapter's public surface. |
</details>
<!-- greptile_failed_comments -->
<details open><summary><h3>Comments Outside Diff (2)</h3></summary>
1. packages/adapters/openclaw-gateway/src/server/test.ts, line 281-290 ([link](https://github.com/paperclipai/paperclip/blob/f9a89328eca0eccbe0d83e7dfed18ba0b3504b0d/packages/adapters/openclaw-gateway/src/server/test.ts#L281-L290))
<a href="#"><img alt="P2" src="https://greptile-static-assets.s3.amazonaws.com/badges/p2.svg?v=7" align="top"></a> Test assumes nothing is listening on loopback port 18789
The renamed test ("uses default loopback gateway URL when url is omitted") passes {} as config, which now resolves to ws://127.0.0.1:18789. In a CI environment or developer machine where OpenClaw is actually running on that port, the probe succeeds and exitCode would be 0, not 1 — making the expect(result.exitCode).toBe(1) assertion fail. Consider asserting on the absence of the old error code only:
ts
// Only assert what the PR actually wants to guarantee:
expect(result.errorCode).not.toBe("openclaw_gateway_url_missing");
// exitCode could be 0 (server running) or 1 (connection refused); don't assert on it.
<details><summary>Prompt To Fix With AI</summary>
`````markdown
This is a comment left during a code review.
Path: packages/adapters/openclaw-gateway/src/server/test.ts
Line: 281-290
Comment:
Test assumes nothing is listening on loopback port 18789
The renamed test ("uses default loopback gateway URL when url is omitted") passes {} as config, which now resolves to ws://127.0.0.1:18789. In a CI environment or developer machine where OpenClaw is actually running on that port, the probe succeeds and exitCode would be 0, not 1 — making the expect(result.exitCode).toBe(1) assertion fail. Consider asserting on the absence of the old error code only:
ts
// Only assert what the PR actually wants to guarantee:
expect(result.errorCode).not.toBe("openclaw_gateway_url_missing");
// exitCode could be 0 (server running) or 1 (connection refused); don't assert on it.
How can I resolve this? If you propose a fix, please make it concise.
`````
</details>
2. packages/adapters/openclaw-gateway/src/server/execute.ts, line 335-339 ([link](https://github.com/paperclipai/paperclip/blob/f9a89328eca0eccbe0d83e7dfed18ba0b3504b0d/packages/adapters/openclaw-gateway/src/server/execute.ts#L335-L339))
<a href="#"><img alt="P2" src="https://greptile-static-assets.s3.amazonaws.com/badges/p2.svg?v=7" align="top"></a> resolveClaimedApiKeyPath defined but never called
resolveClaimedApiKeyPath is declared at line 337 and exposed to the config-driven path (ctx.config.claimedApiKeyPath), but buildWakeText (line 368) hard-codes the same path literal "~/.openclaw/workspace/paperclip-claimed-api-key.json" instead of calling this function. If you intend to let operators override the key path via config, buildWakeText should accept the resolved path as a parameter rather than duplicating the constant.
<details><summary>Prompt To Fix With AI</summary>
`````markdown
This is a comment left during a code review.
Path: packages/adapters/openclaw-gateway/src/server/execute.ts
Line: 335-339
Comment:
resolveClaimedApiKeyPath defined but never called
resolveClaimedApiKeyPath is declared at line 337 and exposed to the config-driven path (ctx.config.claimedApiKeyPath), but buildWakeText (line 368) hard-codes the same path literal "~/.openclaw/workspace/paperclip-claimed-api-key.json" instead of calling this function. If you intend to let operators override the key path via config, buildWakeText should accept the resolved path as a parameter rather than duplicating the constant.
How can I resolve this? If you propose a fix, please make it concise.
``
</details></details>
<!-- /greptile_failed_comments -->
<details><summary>Prompt To Fix All With AI</summary>
``markdown
This is a comment left during a code review.
Path: packages/adapters/claude-local/src/server/parse.ts
Line: 183-186
Comment:
Raw stdout scan can false-positive on assistant text
The first branch tests the entire raw stdout string for phrases like "hit your limit" and "you've hit your limit". Because stdout is stream-JSON, an assistant turn whose text content happens to contain those words (e.g., a model response about API quotas) would trigger a false-rate-limit classification and suppress the claude_hello_probe_passed / _unexpected_output check. The safer approach is to restrict this scan to lines whose type is rate_limit_event or result, rather than matching the full stdout payload:
ts
// Instead of testing the raw stdout blob, only flag specific stream-JSON event types:
const hasRateLimitEvent = stdout.split(/\r?\n/).some((line) => {
try {
const ev = JSON.parse(line.trim());
return ev?.type === "rate_limit_event" || ev?.error === "rate_limit";
} catch { return false; }
});
if (hasRateLimitEvent) return true;
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: packages/adapters/openclaw-gateway/src/server/test.ts
Line: 281-290
Comment:
Test assumes nothing is listening on loopback port 18789
The renamed test ("uses default loopback gateway URL when url is omitted") passes {} as config, which now resolves to ws://127.0.0.1:18789. In a CI environment or developer machine where OpenClaw is actually running on that port, the probe succeeds and exitCode would be 0, not 1 — making the expect(result.exitCode).toBe(1) assertion fail. Consider asserting on the absence of the old error code only:
ts
// Only assert what the PR actually wants to guarantee:
expect(result.errorCode).not.toBe("openclaw_gateway_url_missing");
// exitCode could be 0 (server running) or 1 (connection refused); don't assert on it.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: packages/adapters/openclaw-gateway/src/server/execute.ts
Line: 335-339
Comment:
resolveClaimedApiKeyPath defined but never called
resolveClaimedApiKeyPath is declared at line 337 and exposed to the config-driven path (ctx.config.claimedApiKeyPath), but buildWakeText (line 368) hard-codes the same path literal "~/.openclaw/workspace/paperclip-claimed-api-key.json" instead of calling this function. If you intend to let operators override the key path via config, buildWakeText should accept the resolved path as a parameter rather than duplicating the constant.
How can I resolve this? If you propose a fix, please make it concise.
`````
</details>
<sub>Reviews (1): Last reviewed commit: ["fix(claude-local): detect subscription '..."](https://github.com/paperclipai/paperclip/commit/f9a89328eca0eccbe0d83e7dfed18ba0b3504b0d) | [Re-trigger Greptile](https://app.greptile.com/api/retrigger?id=27989993)</sub>