mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-01 16:26:46 +00:00
Surface classified cron failure causes without changing raw cron JSON error text. - add additive CLI `cause` output for finished run entries with `errorReason` - persist/backfill full `FailoverReason` values on cron run-log entries - thread provider context through cron finalization so provider-specific failure causes stay accurate - extend protocol/Swift models and regression coverage for CLI JSON, run-log parsing/search, alerts, and protocol conformance Verification: - `pnpm lint --threads=8` - `pnpm protocol:check` - `pnpm exec oxfmt --check src/cli/cron-cli/shared.ts src/cli/cron-cli/shared.cause-display.test.ts src/cron/run-log.ts src/cron/run-log.error-reason.test.ts src/cron/cron-protocol-conformance.test.ts src/cron/service.failure-alert.test.ts src/cron/service/timer.ts src/cron/service/ops.ts src/gateway/protocol/schema/cron.ts scripts/protocol-gen-swift.ts` - `git diff --check` - AWS Crabbox `cbx_8a6a65ab83b0` / `run_42b73a4a9750`: 4 files, 20 tests passed - autoreview clean, no accepted/actionable findings - GitHub CI/CodeQL/OpenGrep/Workflow Sanity green/skipped/neutral on `aa29b087b2587d0aed3d409de5e7a2c706c32cdf` Co-authored-by: Yoshikazu Terashi <yterashi@peperon-works.jp>
126 lines
3.7 KiB
TypeScript
126 lines
3.7 KiB
TypeScript
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import { readCronRunLogEntriesPage } from "./run-log.js";
|
|
|
|
describe("cron run log errorReason", () => {
|
|
it("backfills errorReason from timeout error text for older entries", async () => {
|
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "cron-run-log-"));
|
|
const file = path.join(dir, "job.jsonl");
|
|
await fs.writeFile(
|
|
file,
|
|
`${JSON.stringify({
|
|
ts: 1,
|
|
jobId: "job-1",
|
|
action: "finished",
|
|
status: "error",
|
|
error: "cron: job execution timed out",
|
|
})}\n`,
|
|
"utf8",
|
|
);
|
|
|
|
const page = await readCronRunLogEntriesPage(file, { limit: 10 });
|
|
expect(page.entries[0]?.errorReason).toBe("timeout");
|
|
});
|
|
|
|
it("validates persisted errorReason against the full failover reason set", async () => {
|
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "cron-run-log-"));
|
|
const file = path.join(dir, "job.jsonl");
|
|
const reasons = [
|
|
"auth",
|
|
"auth_permanent",
|
|
"format",
|
|
"rate_limit",
|
|
"overloaded",
|
|
"billing",
|
|
"server_error",
|
|
"timeout",
|
|
"model_not_found",
|
|
"session_expired",
|
|
"empty_response",
|
|
"no_error_details",
|
|
"unclassified",
|
|
"unknown",
|
|
];
|
|
await fs.writeFile(
|
|
file,
|
|
reasons
|
|
.map((errorReason, index) =>
|
|
JSON.stringify({
|
|
ts: index + 1,
|
|
jobId: "job-1",
|
|
action: "finished",
|
|
status: "error",
|
|
errorReason,
|
|
}),
|
|
)
|
|
.join("\n") + "\n",
|
|
"utf8",
|
|
);
|
|
|
|
const page = await readCronRunLogEntriesPage(file, { limit: 50, sortDir: "asc" });
|
|
expect(page.entries.map((entry) => entry.errorReason)).toEqual(reasons);
|
|
});
|
|
|
|
it("derives an invalid persisted reason from raw error text before exposing entries", async () => {
|
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "cron-run-log-"));
|
|
const file = path.join(dir, "job.jsonl");
|
|
await fs.writeFile(
|
|
file,
|
|
`${JSON.stringify({
|
|
ts: 1,
|
|
jobId: "job-1",
|
|
action: "finished",
|
|
status: "error",
|
|
error: "upstream unavailable: 503 overloaded",
|
|
errorReason: "not-a-real-reason",
|
|
})}\n`,
|
|
"utf8",
|
|
);
|
|
|
|
const page = await readCronRunLogEntriesPage(file, { limit: 10 });
|
|
expect(page.entries[0]?.errorReason).toBe("overloaded");
|
|
});
|
|
|
|
it("uses provider context when deriving persisted run-log reasons", async () => {
|
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "cron-run-log-"));
|
|
const file = path.join(dir, "job.jsonl");
|
|
await fs.writeFile(
|
|
file,
|
|
`${JSON.stringify({
|
|
ts: 1,
|
|
jobId: "job-1",
|
|
action: "finished",
|
|
status: "error",
|
|
error: "403 Key limit exceeded (monthly limit)",
|
|
provider: "openrouter",
|
|
})}\n`,
|
|
"utf8",
|
|
);
|
|
|
|
const page = await readCronRunLogEntriesPage(file, { limit: 10 });
|
|
expect(page.entries[0]?.errorReason).toBe("billing");
|
|
});
|
|
|
|
it("includes derived errorReason values in run-log search", async () => {
|
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "cron-run-log-"));
|
|
const file = path.join(dir, "job.jsonl");
|
|
await fs.writeFile(
|
|
file,
|
|
`${JSON.stringify({
|
|
ts: 1,
|
|
jobId: "job-1",
|
|
action: "finished",
|
|
status: "error",
|
|
error: "cron: job execution timed out",
|
|
})}\n`,
|
|
"utf8",
|
|
);
|
|
|
|
const page = await readCronRunLogEntriesPage(file, { limit: 10, query: "timeout" });
|
|
expect(page.entries).toHaveLength(1);
|
|
expect(page.entries[0]?.errorReason).toBe("timeout");
|
|
});
|
|
});
|