fix: onboarding misreports cron parse failure as gateway error (#23)

When OpenClaw CLI prints debug log lines (e.g. "[plugins] [debug] ...")
to stdout before JSON, extractJson() failed because bracketed log lines
look like JSON array starts. Now tries each candidate position until one
actually parses. Also fixes the onboarding wizard to show a specific
"CLI log output" error instead of the misleading "Could not reach
gateway" message when /api/crons fails due to JSON parsing. Bumps to
v0.8.6.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
JohnRiceML
2026-03-23 10:18:00 -05:00
co-authored by Claude Opus 4.6
parent 6b61daa6f6
commit 7c44a6d969
5 changed files with 57 additions and 19 deletions
+13 -4
View File
@@ -164,15 +164,24 @@ export function OnboardingWizard({ forceOpen, onClose }: OnboardingWizardProps)
// Check crons (validates gateway + openclaw binary)
fetch('/api/crons')
.then(r => {
if (!r.ok) throw new Error(`HTTP ${r.status}`)
.then(async r => {
if (!r.ok) {
const body = await r.json().catch(() => null)
const serverMsg = body?.error
throw new Error(serverMsg || `HTTP ${r.status}`)
}
return r.json()
})
.then(() => {
setCronsStatus('ok')
})
.catch(() => {
setCronsError('Could not reach OpenClaw gateway. Run: openclaw gateway run')
.catch((err: Error) => {
const msg = err.message || ''
if (msg.includes('Failed to fetch cron') || msg.includes('JSON') || msg.includes('Unexpected token')) {
setCronsError('Cron list failed -- OpenClaw CLI may be printing log output before JSON. Try: OPENCLAW_LOG_LEVEL=error clawport dev')
} else {
setCronsError('Could not reach OpenClaw gateway. Run: openclaw gateway run')
}
setCronsStatus('error')
})
}
+16
View File
@@ -41,4 +41,20 @@ hooks: Unrecognized key: "allowedAgentIds"
it('throws on empty string', () => {
expect(() => extractJson('')).toThrow()
})
it('skips bracketed log lines like [plugins] before real JSON', () => {
const raw = `[plugins] [debug] Database schema initialized
[plugins] [debug] Loading context engine
{"jobs":[{"name":"daily-report","schedule":"0 8 * * *"}]}`
const result = extractJson(raw) as Record<string, unknown>
expect(result.jobs).toEqual([{ name: 'daily-report', schedule: '0 8 * * *' }])
})
it('skips bracketed log lines before JSON array', () => {
const raw = `[plugins] [debug] Database schema initialized
[plugins] [info] Ready
[{"id":"pulse","name":"daily-pulse"}]`
const result = extractJson(raw)
expect(result).toEqual([{ id: 'pulse', name: 'daily-pulse' }])
})
})
+25 -12
View File
@@ -1,25 +1,38 @@
/**
* Extract a JSON value from CLI output that may contain non-JSON preamble.
*
* Some OpenClaw versions print validation warnings (e.g. "Unrecognized key")
* to stdout before the JSON payload. This function finds the first `[` or `{`
* and parses from there, so ClawPort doesn't break on noisy CLI output.
* Some OpenClaw versions print validation warnings or debug log lines
* (e.g. "[plugins] [debug] ...") to stdout before the JSON payload.
* This function finds the actual JSON structure by trying each `[` or `{`
* position until one parses successfully.
*/
export function extractJson(raw: string): unknown {
// Fast path: raw is already valid JSON
const trimmed = raw.trim()
if (trimmed.startsWith('[') || trimmed.startsWith('{')) {
return JSON.parse(trimmed)
try {
return JSON.parse(trimmed)
} catch {
// May start with [ but be a log line like "[plugins] ..." -- fall through
}
}
// Find the first JSON structure in the output
const arrStart = raw.indexOf('[')
const objStart = raw.indexOf('{')
const starts = [arrStart, objStart].filter(i => i >= 0)
if (starts.length === 0) {
throw new SyntaxError('No JSON found in CLI output')
// Try each potential JSON start position
let pos = 0
while (pos < raw.length) {
const arrStart = raw.indexOf('[', pos)
const objStart = raw.indexOf('{', pos)
const candidates = [arrStart, objStart].filter(i => i >= 0)
if (candidates.length === 0) break
const start = Math.min(...candidates)
try {
return JSON.parse(raw.slice(start))
} catch {
// This wasn't the real JSON start -- advance past it
pos = start + 1
}
}
const start = Math.min(...starts)
return JSON.parse(raw.slice(start))
throw new SyntaxError('No JSON found in CLI output')
}
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "clawport-ui",
"version": "0.8.5",
"version": "0.8.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "clawport-ui",
"version": "0.8.5",
"version": "0.8.6",
"license": "MIT",
"dependencies": {
"@dagrejs/dagre": "^2.0.4",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "clawport-ui",
"version": "0.8.5",
"version": "0.8.6",
"description": "Open-source dashboard for managing, monitoring, and chatting with your OpenClaw AI agents.",
"homepage": "https://clawport.dev",
"repository": {