Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 61 additions & 2 deletions REVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -590,10 +590,69 @@ Verified on a real Alpine 3.21 machine, driving the compiled binary through a PT

Added to the release matrix, so Alpine-based OpenClaw containers get a working binary.

## Still open (not fixed)
## Resolved in the third pass

**Payments are received but never recorded.** `src/routes/payment.js` handles `order_created`
**Payments are received but never recorded** — the payment surface was removed entirely. Original finding: `src/routes/payment.js` handles `order_created`
with `// TODO: Mark fix as paid in database`. With Lemon Squeezy configured a user can be charged
$2 and nothing in the system records it or unlocks anything. The checkout path is otherwise
sound. This needs a product decision — record and gate on payment, or remove the paid path until
it is real — so it is flagged rather than guessed at.


---

# Third pass — payment removal and real break-fix scenarios

## Payment surface removed

`/api/checkout` created real Lemon Squeezy sessions while `order_created` was
`// TODO: Mark fix as paid in database`, so a configured store could charge $2 and record
nothing. Removed the checkout endpoint, the payment page, the payment webhook and the unused
Lemon Squeezy verifier. Svix verification stays — the Resend inbound-email webhook uses it.
`security-regressions` now asserts those routes stay 404.

## Five break-fix scenarios on a real OpenClaw 2026.6.11 install

Each scenario resets the box to a valid config, breaks one real thing, and scans.

| # | Broken | ClawFix reported | Detected |
|---|---|---|---|
| 1 | Gateway process killed | `[critical] Gateway is not running` | **yes** |
| 2 | Port 18789 held by an unrelated process | `[critical] Port conflict detected` + `Gateway port 18789 is occupied by python3 (PID 726), but OpenClaw cannot reach it` | **yes** |
| 3 | `openclaw.json` corrupted | `[high] JSON5 parse failed: SyntaxError: JSON5: invalid character 'i' at 1:8` | **yes** |
| 4 | Gateway auth set to `none` on loopback | `[critical] Gateway auth missing on loopback` + `[medium] Gateway HTTP APIs are reachable without auth` | **yes** |
| 5 | `update.auto.enabled = true` | `[medium] Auto-update enabled (risk of restart loops)` | **yes** |

**Detection: 5/5.** Scenario 2 is notably good — it names the squatting process and PID.

## Repairing: the honest picture

The guarded-repair pipeline ran end to end against the real install: plan built locally,
`invalid_token` rejected, single-use token enforced (`token_reused`), apply executed the real
`openclaw gateway restart`, verify re-checked the port, and the outcome was reported as
`verify_failed` — with a rescan agreeing the gateway was still down.

That is the correct behaviour, and it is the B-24 fix working: before this pass the same run
would have claimed `applied`.

But it is not a fix, and the reason matters:

**ClawFix ships exactly one executable repair, and it cannot work in a container.**
`gateway-not-running` invokes `openclaw gateway restart`, which needs systemd or launchd. On
this Alpine host OpenClaw answered *"Gateway service disabled … systemd user services are
unavailable"*, so the repair could not succeed no matter how correct the plumbing is. Nothing in
the catalog can repair scenarios 2–5 either: the four other real problems ClawFix detects have
no repair at all.

So today ClawFix is a strong **diagnostic** with one repair that only applies on service-managed
hosts. The site and README should say that plainly, and the catalog is the obvious place to
invest next — port-conflict and config-invalid are both detected precisely enough to repair.

## Two quality issues seen only in real output

- A finding surfaced as `[critical] timeout` with no other context — a native collector timing
out is being presented to the user as a critical OpenClaw problem
(`cli/core/diagnostics.js`, `text: finding.title || finding.message`).
- Raw parser output is used as a user-facing title: `JSON5 parse failed: SyntaxError: JSON5:
invalid character 'i' at 1:8`. Accurate, but it is a stack-trace fragment where a sentence
belongs.
16 changes: 9 additions & 7 deletions scripts/prove-remediation.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,17 @@ await check('unknown Codex findings cannot inherit unrelated repairs', async ()
}]), []);
});

await check('results and payment routes validate fix IDs', async () => {
const [results, payment] = await Promise.all([
text('src/routes/results.js'),
text('src/routes/payment.js'),
]);
await check('results route validates fix IDs and the payment surface stays removed', async () => {
const results = await text('src/routes/results.js');
assert.match(results, /validateFixId\(req\.params\.fixId\)/);
assert.match(results, /escapeHtml\(data\.analysis/);
assert.match(payment, /validateFixId\(req\.params\.fixId\)/);
assert.match(payment, /validateFixId\(req\.body\?\.fixId\)/);

// Payments were removed: checkout created real Lemon Squeezy sessions while the
// order_created handler was a TODO, so a store could charge and record nothing.
const { existsSync } = await import('node:fs');
assert.equal(existsSync(new URL('../src/routes/payment.js', import.meta.url)), false);
const server = await text('src/server.js');
assert.doesNotMatch(server, /paymentRouter/);
});

await check('CLI requires consent and recursively redacts every upload', async () => {
Expand Down
4 changes: 2 additions & 2 deletions src/landing.js
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@ gateway-restart repair. Want me to?
<div class="step">
<div class="step-num">3</div>
<h3>Review & Apply</h3>
<p>You get a commented fix script. Read it, understand it, then run it. Nothing happens without your approval.</p>
<p>You get a commented fix script. Read it, understand it, then run it. Nothing happens without your approval. One reviewed repair runs automatically today — restarting a stopped gateway on a host with systemd or launchd; everything else is diagnosis and guidance.</p>
</div>
</div>
</div>
Expand Down Expand Up @@ -767,7 +767,7 @@ gateway-restart repair. Want me to?
<span class="badge" style="background:var(--green);">Hosted</span>
<div class="price free-tag">Free</div>
<h3>MIT source, optional hosting</h3>
<p>The CLI and server are MIT licensed. <code>clawfix.dev</code> is currently free to use.</p>
<p>The CLI and server are MIT licensed. <code>clawfix.dev</code> is free to use, with no paid tier and no payment path in the product.</p>
<p style="margin-top:12px;color:var(--muted);font-size:0.8rem;">Hosted limits may change. Self-hosting remains available under the MIT license.</p>
</div>
</div>
Expand Down
245 changes: 0 additions & 245 deletions src/routes/payment.js

This file was deleted.

6 changes: 3 additions & 3 deletions src/routes/results.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,11 +222,11 @@ function resultsPage(fixId) {
html += '</div>';
}

// Beta notice (no payment during beta)
// Free and open source — there is no paid tier.
if (count > 0) {
html += '<div class="payment" style="border-color: var(--green);">';
html += '<h3>🎉 Free During Beta!</h3>';
html += '<p>This diagnosis is on us. Enjoy full AI analysis for free while we\\'re in early access.<br>';
html += '<h3>🎉 Free and open source</h3>';
html += '<p>ClawFix is MIT licensed and free to use.<br>';
html += 'Like ClawFix? Star us on <a href="https://github.com/arcabotai/clawfix" style="color:var(--green)">GitHub</a> — it helps a lot.</p>';
html += '</div>';
}
Expand Down
3 changes: 0 additions & 3 deletions src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { healthRouter } from './routes/health.js';
import { scriptRouter } from './routes/script.js';
import { installRouter } from './routes/install.js';
import { resultsRouter } from './routes/results.js';
import { paymentRouter } from './routes/payment.js';
import { webhooksRouter } from './routes/webhooks.js';
import { chatRouter } from './routes/chat.js';
import { agentV2Router } from './routes/agent-v2.js';
Expand Down Expand Up @@ -59,8 +58,6 @@ app.use('/api', diagnoseRouter);
app.use('/api', healthRouter);
app.use('/api', chatRouter); // POST /api/chat — interactive TUI chat
app.use('/api', agentV2Router); // POST /api/v2/agent/messages — constrained agent protocol
app.use('/api', paymentRouter); // POST /api/checkout, /api/webhook/lemonsqueezy
app.use('/', paymentRouter); // GET /pay/:fixId — payment page
app.use('/', webhooksRouter); // POST /webhooks/resend — inbound email
app.use('/', scriptRouter); // GET /fix — diagnostic script
app.use('/', installRouter); // GET /install — bash installer
Expand Down
Loading