fix: add job cancel and tune artifact download

This commit is contained in:
Codex
2026-06-02 09:20:43 +00:00
parent 263b0cf3b2
commit 66ec934160
5 changed files with 61 additions and 16 deletions
+2
View File
@@ -1677,6 +1677,8 @@ function downloadRemoteFile(options: ArtifactRegistryOptions, remotePath: string
"ssh",
options.providerId,
"download",
"--chunk-bytes",
"64000",
remotePath,
localPath,
], repoRoot, { timeoutMs });
+4 -2
View File
@@ -84,6 +84,7 @@ export function rootHelp(): unknown {
{ command: "codex (queues [--full|--all] | queue create <queueId> | queue merge <sourceQueueId> --into <targetQueueId> | move <taskId> --queue <queueId>)", description: "List low-noise queue summaries by default, including effective activity counts that distinguish scheduler-local queues, DB running tasks, and heartbeat-fresh runners; full queue rows require --full/--all." },
{ command: "job list [--limit N] [--include-command]", description: "List async jobs from .state/jobs with a bounded default page and progress summaries." },
{ command: "job status <jobId|latest> [--tail-bytes N]", description: "Show job state with a structured progress summary and bounded stdout/stderr tails." },
{ command: "job cancel <jobId>", description: "Cancel a queued/running async job through the .state/jobs control entry and keep a terminal canceled record." },
{ command: "debug health", description: "Probe internal core, nodes, system/Docker status, frontend, provider ingress, and public boundary." },
{ command: "debug dispatch [providerId] [docker.ps|provider.upgrade|host.ssh|microservice.http|echo] [--wait-ms N]", description: "Submit a real internal-core dispatch request for CLI debugging." },
{ command: "debug task <taskId|latest>", description: "Read a dispatched task record from internal core for CLI debugging." },
@@ -480,13 +481,14 @@ function codexHelp(): unknown {
function jobHelp(): unknown {
return {
command: "job list|status",
command: "job list|status|cancel",
output: "json",
usage: [
"bun scripts/cli.ts job list [--limit N] [--include-command]",
"bun scripts/cli.ts job status <jobId|latest> [--tail-bytes N]",
"bun scripts/cli.ts job cancel <jobId>",
],
description: "Inspect fire-and-forget job state from .state/jobs with structured progress summaries and bounded log tails.",
description: "Inspect or cancel fire-and-forget job state from .state/jobs with structured progress summaries and bounded log tails.",
};
}
+47 -11
View File
@@ -4,7 +4,7 @@ import { join } from "node:path";
import { repoRoot, rootPath } from "./config";
import { runCommandToFiles, tailFile } from "./command";
export type JobStatus = "queued" | "running" | "succeeded" | "failed";
export type JobStatus = "queued" | "running" | "succeeded" | "failed" | "canceled";
export interface JobRecord {
id: string;
@@ -68,6 +68,34 @@ export function readJob(id: string): JobRecord {
return JSON.parse(readFileSync(path, "utf8")) as JobRecord;
}
export function cancelJob(id: string): unknown {
const job = readJob(id);
if (job.status !== "queued" && job.status !== "running") {
return { ok: true, action: "cancel-job", id, alreadyTerminal: true, job };
}
const actions: Array<Record<string, unknown>> = [];
if (job.runnerPid !== null && job.runnerPid !== undefined) {
try {
process.kill(-job.runnerPid, "SIGTERM");
actions.push({ signal: "SIGTERM", target: "process-group", pid: job.runnerPid, ok: true });
} catch (error) {
actions.push({ signal: "SIGTERM", target: "process-group", pid: job.runnerPid, ok: false, error: error instanceof Error ? error.message : String(error) });
try {
process.kill(job.runnerPid, "SIGTERM");
actions.push({ signal: "SIGTERM", target: "process", pid: job.runnerPid, ok: true });
} catch (innerError) {
actions.push({ signal: "SIGTERM", target: "process", pid: job.runnerPid, ok: false, error: innerError instanceof Error ? innerError.message : String(innerError) });
}
}
}
job.status = "canceled";
job.finishedAt = new Date().toISOString();
job.exitCode = 130;
writeFileSync(job.stderrFile, `${tailFile(job.stderrFile, 512_000)}${JSON.stringify({ event: "unidesk.job.cancel", at: job.finishedAt, jobId: id, actions })}\n`, "utf8");
writeJob(job);
return { ok: true, action: "cancel-job", id, actions, job };
}
export function listJobs(): JobRecord[] {
return readdirSync(jobsDir())
.filter((name) => name.endsWith(".json"))
@@ -323,32 +351,40 @@ function summarizeGitMirrorJobProgress(job: JobRecord, stdoutTail: string, stder
function genericJobProgress(job: JobRecord): JobProgressSummary {
const nowMs = Date.now();
const stderrTail = tailFile(job.stderrFile, 96_000);
const downloadEvents = parseJsonLineEvents(stderrTail, "unidesk.ssh.download.progress");
const lastDownload = downloadEvents.at(-1) ?? null;
const lastDownloadAt = stringField(lastDownload?.at);
const lastEventAgeSeconds = lastDownloadAt === null ? null : secondsSince(lastDownloadAt, job.finishedAt ?? nowMs);
const elapsedSeconds = jobElapsedSeconds(job, nowMs);
const warnings = jobProgressWarnings({
job,
eventsObserved: 0,
eventsObserved: downloadEvents.length,
elapsedSeconds,
stage: null,
stageStatus: null,
stage: lastDownload === null ? null : "ssh-download",
stageStatus: lastDownload === null ? null : "running",
stageElapsedSeconds: null,
lastEventAgeSeconds: null,
lastEventAgeSeconds,
});
const downloadSummary = lastDownload === null
? null
: ` ssh-download ${Number(lastDownload.actualBytes ?? 0)}/${Number(lastDownload.expectedBytes ?? 0)} bytes chunks=${Number(lastDownload.chunks ?? 0)}`;
return {
kind: "generic",
stage: null,
stageStatus: null,
stage: lastDownload === null ? null : "ssh-download",
stageStatus: lastDownload === null ? null : "running",
sourceCommit: null,
pipelineRun: null,
pipelineCreated: null,
elapsedSeconds,
stageElapsedSeconds: null,
lastEventAt: null,
lastEventAgeSeconds: null,
eventsObserved: 0,
lastEventAt: lastDownloadAt,
lastEventAgeSeconds,
eventsObserved: downloadEvents.length,
slow: warnings.length > 0,
warnings,
timings: {},
summary: `${job.status}${job.exitCode === null ? "" : ` exit=${job.exitCode}`}`,
summary: `${job.status}${job.exitCode === null ? "" : ` exit=${job.exitCode}`}${downloadSummary ?? ""}`,
nextCommand: job.status === "running" ? `bun scripts/cli.ts job status ${job.id} --tail-bytes 12000` : null,
};
}