fix: cron panel crash on missing schedule + doctor parser false positive (#347)

- Filter out cron jobs with missing/empty schedule before processing,
  preventing TypeError on .toLowerCase() (#342)
- Add defensive .localeCompare() guard in sort comparator
- Exclude positive/instructional lines ("No ... warnings detected",
  "Run: ...") from doctor issue extraction (#331)
- Strip negated warning phrases before keyword detection so "No channel
  security warnings detected" doesn't trigger warning level
- Add 2 tests for the doctor parser fix
This commit is contained in:
nyk 2026-03-14 16:21:04 +07:00 committed by GitHub
parent 8eef4139ec
commit 466a1621d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 41 additions and 4 deletions

View File

@ -526,6 +526,7 @@ export function CronManagementPanel() {
)
const filteredJobs = cronJobs
.filter((job) => typeof job.schedule === 'string' && job.schedule.length > 0)
.filter((job) => {
const query = searchQuery.trim().toLowerCase()
const matchesQuery =
@ -563,7 +564,7 @@ export function CronManagementPanel() {
case 'name':
return dir * a.name.localeCompare(b.name)
case 'schedule':
return dir * a.schedule.localeCompare(b.schedule)
return dir * (a.schedule || '').localeCompare(b.schedule || '')
case 'lastRun':
return dir * ((a.lastRun || 0) - (b.lastRun || 0))
case 'nextRun':

View File

@ -119,4 +119,31 @@ Run "openclaw doctor --fix" to apply changes.
expect(result.category).toBe('general')
expect(result.canFix).toBe(false)
})
it('treats positive security lines as healthy, not warnings (#331)', () => {
const result = parseOpenClawDoctorOutput(`
? Security
- No channel security warnings detected.
- Run: openclaw security audit --deep
`, 0)
expect(result.healthy).toBe(true)
expect(result.level).toBe('healthy')
expect(result.issues).toEqual([])
})
it('still detects real security warnings alongside positive lines', () => {
const result = parseOpenClawDoctorOutput(`
? Security
- Channel "public" has no auth configured.
- No channel security warnings detected.
- Run: openclaw security audit --deep
`, 0)
expect(result.healthy).toBe(false)
expect(result.level).toBe('warning')
expect(result.issues).toEqual([
'Channel "public" has no auth configured.',
])
})
})

View File

@ -24,6 +24,13 @@ function isSessionAgingLine(line: string): boolean {
return /^agent:[\w:-]+ \(\d+[mh] ago\)$/i.test(line)
}
function isPositiveOrInstructionalLine(line: string): boolean {
return /^no .* warnings? detected/i.test(line) ||
/^no issues/i.test(line) ||
/^run:\s/i.test(line) ||
/^all .* (healthy|ok|valid|passed)/i.test(line)
}
function isDecorativeLine(line: string): boolean {
return /^[▄█▀░\s]+$/.test(line) || /openclaw doctor/i.test(line) || /🦞\s*openclaw\s*🦞/i.test(line)
}
@ -130,10 +137,12 @@ export function parseOpenClawDoctorOutput(
const issues = lines
.filter(line => /^[-*]\s+/.test(line))
.map(line => line.replace(/^[-*]\s+/, '').trim())
.filter(line => !isSessionAgingLine(line) && !isStateDirectoryListLine(line))
.filter(line => !isSessionAgingLine(line) && !isStateDirectoryListLine(line) && !isPositiveOrInstructionalLine(line))
const mentionsWarnings = /\bwarning|warnings|problem|problems|invalid config|fix\b/i.test(raw)
const mentionsHealthy = /\bok\b|\bhealthy\b|\bno issues\b|\bvalid\b/i.test(raw)
// Strip positive/negated phrases before checking for warning keywords
const rawForWarningCheck = raw.replace(/\bno\s+\w+\s+(?:security\s+)?warnings?\s+detected\b/gi, '')
const mentionsWarnings = /\bwarning|warnings|problem|problems|invalid config|fix\b/i.test(rawForWarningCheck)
const mentionsHealthy = /\bok\b|\bhealthy\b|\bno issues\b|\bno\b.*\bwarnings?\s+detected\b|\bvalid\b/i.test(raw)
let level: OpenClawDoctorLevel = 'healthy'
if (exitCode !== 0 || /invalid config|failed|error/i.test(raw)) {