From 4a7ba557ecd0fae2af6dab3a51b3c0c6c48bb411 Mon Sep 17 00:00:00 2001 From: jaeko44 Date: Mon, 23 Mar 2026 08:00:16 +1100 Subject: [PATCH 1/5] wip: save abandoned task work for task-cc3da1b76df6-daf8f02b64 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- task/task-cli.mjs | 40 +++++++++++++++- tests/task-cli.test.mjs | 21 ++++++++ tests/workflow-templates.test.mjs | 49 ++++++++++++++++++- workflow-templates/task-batch.mjs | 80 +++++++++++++++++++++++++++++-- 4 files changed, 184 insertions(+), 6 deletions(-) diff --git a/task/task-cli.mjs b/task/task-cli.mjs index f4773057..05252808 100755 --- a/task/task-cli.mjs +++ b/task/task-cli.mjs @@ -30,7 +30,7 @@ import { homedir } from "node:os"; import { fileURLToPath } from "node:url"; import { readFileSync, existsSync, statSync } from "node:fs"; import { randomUUID } from "node:crypto"; -import { getTaskLifetimeTotals } from "../infra/runtime-accumulator.mjs"; +import { getTaskLifetimeTotals } from \ const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -1009,7 +1009,6 @@ function computeRepoAreaEffectiveLimit({ export async function taskImport(source) { let tasks; if (typeof source === "string") { - // File path const raw = readFileSync(resolve(source), "utf8"); const parsed = JSON.parse(raw); tasks = parsed.tasks || parsed.backlog || parsed; @@ -1022,6 +1021,24 @@ export async function taskImport(source) { throw new Error("Source must be a file path or array of task objects"); } + try { + tasks = validateTaskBatchPayload(tasks).map((item) => ({ + id: item.taskId, + title: item.taskTitle, + status: item.status, + branch: item.branch, + scope: item.scope, + repository: item.repository, + workspace: item.workspace, + })); + } catch (error) { + if (typeof source === "string") { + const summary = summarizeTaskBatchPayloadForLog(tasks); + throw new Error(`${error.message}; summary=${JSON.stringify(summary)}`); + } + throw error; + } + let created = 0; let failed = 0; const errors = []; @@ -1748,4 +1765,23 @@ if (process.argv[1] && resolve(process.argv[1]) === __filename) { process.exit(1); }); } +async function cliCreateBatch(args) { + const payloadFile = getArgValue(args, "--payload-file") || args.find((a) => !a.startsWith("--")); + if (!payloadFile) { + console.error(" Error: payload file required. Usage: bosun task create-batch --payload-file "); + process.exit(1); + } + if (!existsSync(resolve(payloadFile))) { + console.error(` Error: file not found: ${payloadFile}`); + process.exit(1); + } + + try { + const result = await taskImport(payloadFile); + console.log(JSON.stringify(result, null, 2)); + } catch (err) { + console.error(` Error: ${err.message}`); + process.exit(1); + } +} diff --git a/tests/task-cli.test.mjs b/tests/task-cli.test.mjs index 50e375e3..78386405 100644 --- a/tests/task-cli.test.mjs +++ b/tests/task-cli.test.mjs @@ -338,4 +338,25 @@ describe("task-cli taskStats repo area lock state", () => { logSpy.mockRestore(); }); }); +describe("task-cli task batch payload validation", () => { + it("rejects malformed task-batch payload files with deterministic errors", () => { + const payloadPath = resolve(tempDirs[tempDirs.length - 1] ?? mkdtempSync(resolve(tmpdir(), "bosun-task-cli-")), "task-batch-invalid.json"); + writeFileSync(payloadPath, JSON.stringify([{ taskId: "", repository: "virtengine/bosun", workspace: "virtengine-gh" }]), "utf8"); + const result = spawnSync( + process.execPath, + ["task/task-cli.mjs", "create-batch", "--payload-file", payloadPath], + { + cwd: process.cwd(), + env: { ...process.env }, + encoding: "utf8", + }, + ); + + expect(result.status).not.toBe(0); + expect(result.stderr).toContain( + "Invalid task-batch payload: item[0].taskId must be a non-empty string", + ); + expect(result.stderr).toContain('summary={"type":"array","count":1,"taskIds":[]}'); + }); +}); diff --git a/tests/workflow-templates.test.mjs b/tests/workflow-templates.test.mjs index 3a4b2c08..3d219619 100644 --- a/tests/workflow-templates.test.mjs +++ b/tests/workflow-templates.test.mjs @@ -18,7 +18,7 @@ import { relayoutInstalledTemplateWorkflows, installTemplate, installTemplateSet, -} from "../workflow/workflow-templates.mjs"; +} from \ import { WorkflowEngine, getNodeType, @@ -1527,3 +1527,50 @@ describe("template category coverage", () => { } }); }); +describe("task batch payload validation", () => { + it("accepts a valid minimal batch item", () => { + const payload = [ + { + taskId: "task-123", + taskTitle: "Ship validation", + status: "todo", + repository: "virtengine/bosun", + workspace: "virtengine-gh", + branch: "task/123", + scope: "workflow", + }, + ]; + + expect(validateTaskBatchPayload(payload)).toEqual(payload); + }); + + it("rejects malformed batch items with deterministic errors", () => { + expect(() => validateTaskBatchPayload([ + { + taskId: "", + taskTitle: "Bad payload", + status: "todo", + repository: "virtengine/bosun", + workspace: "virtengine-gh", + }, + ])).toThrow("Invalid task-batch payload: item[0].taskId must be a non-empty string"); + }); + + it("trims oversized optional fields per contract", () => { + const payload = [ + { + taskId: "task-oversized", + taskTitle: "Trim optional fields", + status: "todo", + repository: "virtengine/bosun", + workspace: "virtengine-gh", + branch: "b".repeat(200), + scope: "s".repeat(200), + }, + ]; + + const [item] = validateTaskBatchPayload(payload); + expect(item.branch).toBe("b".repeat(128)); + expect(item.scope).toBe("s".repeat(128)); + }); +}); diff --git a/workflow-templates/task-batch.mjs b/workflow-templates/task-batch.mjs index ae5e1b23..197b5086 100644 --- a/workflow-templates/task-batch.mjs +++ b/workflow-templates/task-batch.mjs @@ -23,6 +23,62 @@ import { node, edge, resetLayout } from "./_helpers.mjs"; +const TASK_BATCH_OPTIONAL_FIELD_MAX_LENGTH = 128; + +function normalizeTaskBatchRequiredString(value, field, index) { + if (typeof value !== "string") { + throw new Error(`Invalid task-batch payload: item[${index}].${field} must be a non-empty string`); + } + const trimmed = value.trim(); + if (!trimmed) { + throw new Error(`Invalid task-batch payload: item[${index}].${field} must be a non-empty string`); + } + return trimmed; +} + +function normalizeTaskBatchOptionalString(value) { + if (value == null) return null; + if (typeof value !== "string") { + return String(value).trim().slice(0, TASK_BATCH_OPTIONAL_FIELD_MAX_LENGTH) || null; + } + const trimmed = value.trim(); + if (!trimmed) return null; + return trimmed.slice(0, TASK_BATCH_OPTIONAL_FIELD_MAX_LENGTH); +} + +export function summarizeTaskBatchPayloadForLog(payload) { + if (!Array.isArray(payload)) { + return { type: typeof payload, count: 0 }; + } + return { + type: "array", + count: payload.length, + taskIds: payload.slice(0, 3).map((item) => String(item?.taskId || "").trim()).filter(Boolean), + }; +} + +export function validateTaskBatchPayload(payload) { + if (!Array.isArray(payload)) { + throw new Error("Invalid task-batch payload: payload must be an array"); + } + + return payload.map((item, index) => { + if (!item || typeof item !== "object" || Array.isArray(item)) { + throw new Error(`Invalid task-batch payload: item[${index}] must be an object`); + } + + return { + taskId: normalizeTaskBatchRequiredString(item.taskId, "taskId", index), + taskTitle: normalizeTaskBatchRequiredString(item.taskTitle, "taskTitle", index), + status: normalizeTaskBatchRequiredString(item.status, "status", index), + repository: normalizeTaskBatchRequiredString(item.repository, "repository", index), + workspace: normalizeTaskBatchRequiredString(item.workspace, "workspace", index), + branch: normalizeTaskBatchOptionalString(item.branch), + scope: normalizeTaskBatchOptionalString(item.scope), + }; + }); +} + // ═══════════════════════════════════════════════════════════════════════════ // Task Batch Processor — Parallel Task Dispatch // ═══════════════════════════════════════════════════════════════════════════ @@ -86,7 +142,7 @@ export const TASK_BATCH_PROCESSOR_TEMPLATE = { return task && task.status === "todo" && !task.draft && repository.length > 0 && workspace.length > 0; }); const batch = filtered.slice(0, parseInt(process.env.MAX_BATCH || "10")); - console.log(JSON.stringify(batch.map(t => ({ + const payload = batch.map(t => ({ taskId: t.id, taskTitle: t.title || t.id, status: t.status, @@ -94,7 +150,15 @@ export const TASK_BATCH_PROCESSOR_TEMPLATE = { scope: t.scope || t.metadata?.scope || null, repository: typeof t?.repository === "string" ? t.repository.trim() : null, workspace: typeof t?.workspace === "string" ? t.workspace.trim() : null, - })))); + })); + const { validateTaskBatchPayload, summarizeTaskBatchPayloadForLog } = await import(pathToFileURL(path.join(repoRoot, "workflow-templates", "task-batch.mjs")).href); + try { + console.log(JSON.stringify(validateTaskBatchPayload(payload))); + } catch (error) { + const summary = summarizeTaskBatchPayloadForLog(payload); + console.error(`Invalid task-batch payload: ${error.message.replace(/^Invalid task-batch payload:\s*/, "")}; summary=${JSON.stringify(summary)}`); + process.exit(1); + } }) .catch(e => { console.error(e.message); process.exit(1); }); `], @@ -218,7 +282,15 @@ export const TASK_BATCH_PR_TEMPLATE = { branch: t.branch || t.metadata?.branch || null, repository: t.repository || null, workspace: t.workspace || null, - })))); + })); + const { validateTaskBatchPayload, summarizeTaskBatchPayloadForLog } = await import(pathToFileURL(path.join(repoRoot, "workflow-templates", "task-batch.mjs")).href); + try { + console.log(JSON.stringify(validateTaskBatchPayload(payload))); + } catch (error) { + const summary = summarizeTaskBatchPayloadForLog(payload); + console.error(`Invalid task-batch payload: ${error.message.replace(/^Invalid task-batch payload:\s*/, "")}; summary=${JSON.stringify(summary)}`); + process.exit(1); + } }) .catch(e => { console.error(e.message); process.exit(1); }); `], @@ -323,3 +395,5 @@ export const TASK_BATCH_PR_TEMPLATE = { requiredTemplates: ["template-bosun-pr-progressor"], }, }; + + From 0be8999e90149d5a2806931f0e10afd292e974a1 Mon Sep 17 00:00:00 2001 From: Jonathan Philipos Date: Thu, 26 Mar 2026 09:54:49 +1100 Subject: [PATCH 2/5] Apply 3 code review suggestions Files: task/task-cli.mjs --- task/task-cli.mjs | 66 ++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 35 deletions(-) mode change 100755 => 100644 task/task-cli.mjs diff --git a/task/task-cli.mjs b/task/task-cli.mjs old mode 100755 new mode 100644 index 05252808..b349133f --- a/task/task-cli.mjs +++ b/task/task-cli.mjs @@ -30,7 +30,7 @@ import { homedir } from "node:os"; import { fileURLToPath } from "node:url"; import { readFileSync, existsSync, statSync } from "node:fs"; import { randomUUID } from "node:crypto"; -import { getTaskLifetimeTotals } from \ +import { getTaskLifetimeTotals } from "./task-stats.mjs"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -1021,22 +1021,37 @@ export async function taskImport(source) { throw new Error("Source must be a file path or array of task objects"); } - try { - tasks = validateTaskBatchPayload(tasks).map((item) => ({ - id: item.taskId, - title: item.taskTitle, - status: item.status, - branch: item.branch, - scope: item.scope, - repository: item.repository, - workspace: item.workspace, - })); - } catch (error) { - if (typeof source === "string") { - const summary = summarizeTaskBatchPayloadForLog(tasks); - throw new Error(`${error.message}; summary=${JSON.stringify(summary)}`); + // Support both legacy plain task objects and new task-batch payloads. + // Detect batch-like payloads (with taskId/taskTitle fields) and only then + // apply batch validation + remapping to preserve backwards compatibility. + const looksLikeBatchPayload = + Array.isArray(tasks) && + tasks.length > 0 && + tasks.every( + (item) => + item && + typeof item === "object" && + ("taskId" in item || "taskTitle" in item), + ); + + if (looksLikeBatchPayload) { + try { + tasks = validateTaskBatchPayload(tasks).map((item) => ({ + id: item.taskId, + title: item.taskTitle, + status: item.status, + branch: item.branch, + scope: item.scope, + repository: item.repository, + workspace: item.workspace, + })); + } catch (error) { + if (typeof source === "string") { + const summary = summarizeTaskBatchPayloadForLog(tasks); + throw new Error(`${error.message}; summary=${JSON.stringify(summary)}`); + } + throw error; } - throw error; } let created = 0; @@ -1765,23 +1780,4 @@ if (process.argv[1] && resolve(process.argv[1]) === __filename) { process.exit(1); }); } -async function cliCreateBatch(args) { - const payloadFile = getArgValue(args, "--payload-file") || args.find((a) => !a.startsWith("--")); - if (!payloadFile) { - console.error(" Error: payload file required. Usage: bosun task create-batch --payload-file "); - process.exit(1); - } - - if (!existsSync(resolve(payloadFile))) { - console.error(` Error: file not found: ${payloadFile}`); - process.exit(1); - } - try { - const result = await taskImport(payloadFile); - console.log(JSON.stringify(result, null, 2)); - } catch (err) { - console.error(` Error: ${err.message}`); - process.exit(1); - } -} From 24e57e380b78575e54c50d8822caa5deae1b9474 Mon Sep 17 00:00:00 2001 From: jaeko44 Date: Mon, 30 Mar 2026 22:51:33 +1100 Subject: [PATCH 3/5] fix: repair corrupted import in task-cli.mjs + add task-stats stub --- task/task-cli.mjs | 5 ++++- task/task-stats.mjs | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 task/task-stats.mjs diff --git a/task/task-cli.mjs b/task/task-cli.mjs index caee82c3..6f8edeb2 100644 --- a/task/task-cli.mjs +++ b/task/task-cli.mjs @@ -31,7 +31,10 @@ import { fileURLToPath } from "node:url"; import { readFileSync, existsSync, statSync } from "node:fs"; import { randomUUID } from "node:crypto"; import { getTaskLifetimeTotals } from "./task-stats.mjs"; -import {`r`n normalizeWorkspaceStorageKey,`r`n normalizeWorkspaceStorageKeys,`r`n} from "./task-store.mjs";`r`nimport { getTaskLifetimeTotals } from "./task-stats.mjs"; +import { + normalizeWorkspaceStorageKey, + normalizeWorkspaceStorageKeys, +} from "./task-store.mjs"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); diff --git a/task/task-stats.mjs b/task/task-stats.mjs new file mode 100644 index 00000000..e4cd57da --- /dev/null +++ b/task/task-stats.mjs @@ -0,0 +1,11 @@ +// CLAUDE:SUMMARY — task/task-stats +// Stub module for task lifetime statistics. Returns null until full implementation. + +/** + * Get lifetime totals for a given task ID. + * @param {string} _taskId + * @returns {null} + */ +export function getTaskLifetimeTotals(_taskId) { + return null; +} From c8df804df4e1ba9c724e4de6f6290594151a672a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Mar 2026 14:28:54 +0000 Subject: [PATCH 4/5] fix: resolve CI failures - fix import, add validators, route create-batch, fix tests Agent-Logs-Url: https://github.com/virtengine/bosun/sessions/2bb55cd8-1fc6-4019-8053-6cff534cb72e Co-authored-by: jaeko44 <9289791+jaeko44@users.noreply.github.com> --- task/task-cli.mjs | 48 +++++++++++++++++++++- task/task-stats.mjs | 11 ----- tests/task-cli.test.mjs | 5 ++- tests/workflow-templates.test.mjs | 5 ++- workflow-templates/task-batch.mjs | 67 +++++++++++++++++++++++++++++++ 5 files changed, 121 insertions(+), 15 deletions(-) delete mode 100644 task/task-stats.mjs diff --git a/task/task-cli.mjs b/task/task-cli.mjs index 6f8edeb2..4e110606 100644 --- a/task/task-cli.mjs +++ b/task/task-cli.mjs @@ -30,11 +30,15 @@ import { homedir } from "node:os"; import { fileURLToPath } from "node:url"; import { readFileSync, existsSync, statSync } from "node:fs"; import { randomUUID } from "node:crypto"; -import { getTaskLifetimeTotals } from "./task-stats.mjs"; +import { getTaskLifetimeTotals } from "../infra/runtime-accumulator.mjs"; import { normalizeWorkspaceStorageKey, normalizeWorkspaceStorageKeys, } from "./task-store.mjs"; +import { + validateTaskBatchPayload, + summarizeTaskBatchPayloadForLog, +} from "../workflow-templates/task-batch.mjs"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -1208,6 +1212,8 @@ export async function runTaskCli(args) { return await cliStats(subArgs); case "import": return await cliImport(subArgs); + case "create-batch": + return await cliCreateBatch(subArgs); default: showTaskHelp(); process.exit(subcommand ? 1 : 0); @@ -1696,6 +1702,46 @@ async function cliImport(args) { // ── Help ────────────────────────────────────────────────────────────────────── +async function cliCreateBatch(args) { + if (hasFlag(args, "--help") || hasFlag(args, "-h")) { + console.log(` + bosun task create-batch — Validate and import a task-batch payload file + + USAGE + bosun task create-batch --payload-file + + FILE FORMAT + JSON array of task-batch items. Each item requires: + taskId, taskTitle, status, repository, workspace + + EXAMPLES + bosun task create-batch --payload-file ./batch.json +`); + return; + } + const payloadFile = + getArgValue(args, "--payload-file") || args.find((a) => !a.startsWith("--")); + if (!payloadFile) { + console.error( + " Error: payload file required. Usage: bosun task create-batch --payload-file ", + ); + process.exit(1); + } + + if (!existsSync(resolve(payloadFile))) { + console.error(` Error: file not found: ${payloadFile}`); + process.exit(1); + } + + try { + const result = await taskImport(payloadFile); + console.log(JSON.stringify(result, null, 2)); + } catch (err) { + console.error(` Error: ${err.message}`); + process.exit(1); + } +} + function showCreateHelp() { console.log(` bosun task create — Create a new task diff --git a/task/task-stats.mjs b/task/task-stats.mjs deleted file mode 100644 index e4cd57da..00000000 --- a/task/task-stats.mjs +++ /dev/null @@ -1,11 +0,0 @@ -// CLAUDE:SUMMARY — task/task-stats -// Stub module for task lifetime statistics. Returns null until full implementation. - -/** - * Get lifetime totals for a given task ID. - * @param {string} _taskId - * @returns {null} - */ -export function getTaskLifetimeTotals(_taskId) { - return null; -} diff --git a/tests/task-cli.test.mjs b/tests/task-cli.test.mjs index 03172558..07793172 100644 --- a/tests/task-cli.test.mjs +++ b/tests/task-cli.test.mjs @@ -604,7 +604,8 @@ describe("task-cli taskStats repo area lock state", () => { }); describe("task-cli task batch payload validation", () => { it("rejects malformed task-batch payload files with deterministic errors", () => { - const payloadPath = resolve(tempDirs[tempDirs.length - 1] ?? mkdtempSync(resolve(tmpdir(), "bosun-task-cli-")), "task-batch-invalid.json"); + const storePath = makeTempStorePath(); + const payloadPath = resolve(tempDirs[tempDirs.length - 1], "task-batch-invalid.json"); writeFileSync(payloadPath, JSON.stringify([{ taskId: "", repository: "virtengine/bosun", workspace: "virtengine-gh" }]), "utf8"); const result = spawnSync( @@ -612,7 +613,7 @@ describe("task-cli task batch payload validation", () => { ["task/task-cli.mjs", "create-batch", "--payload-file", payloadPath], { cwd: process.cwd(), - env: { ...process.env }, + env: { ...process.env, BOSUN_STORE_PATH: storePath }, encoding: "utf8", }, ); diff --git a/tests/workflow-templates.test.mjs b/tests/workflow-templates.test.mjs index a7f302c8..c9da4931 100644 --- a/tests/workflow-templates.test.mjs +++ b/tests/workflow-templates.test.mjs @@ -18,7 +18,10 @@ import { relayoutInstalledTemplateWorkflows, installTemplate, installTemplateSet, -} from \ +} from "../workflow/workflow-templates.mjs"; +import { + validateTaskBatchPayload, +} from "../workflow-templates/task-batch.mjs"; import { WorkflowEngine, getNodeType, diff --git a/workflow-templates/task-batch.mjs b/workflow-templates/task-batch.mjs index ded822d3..083e8c58 100644 --- a/workflow-templates/task-batch.mjs +++ b/workflow-templates/task-batch.mjs @@ -316,3 +316,70 @@ export const TASK_BATCH_PR_TEMPLATE = { requiredTemplates: ["template-bosun-pr-progressor"], }, }; + +// ═══════════════════════════════════════════════════════════════════════════ +// Task Batch Payload Validators +// ═══════════════════════════════════════════════════════════════════════════ + +const FIELD_MAX_LEN = 128; + +/** + * Validate and normalise an array of task-batch payload items. + * Required fields: taskId (non-empty string), taskTitle (non-empty string), + * status (non-empty string), repository (non-empty string), + * workspace (non-empty string). + * Optional fields (branch, scope) are truncated to 128 chars. + * + * @param {unknown[]} items + * @returns {object[]} normalised items + * @throws {Error} if any item fails validation + */ +export function validateTaskBatchPayload(items) { + if (!Array.isArray(items)) { + throw new Error("Invalid task-batch payload: expected an array"); + } + return items.map((item, index) => { + if (!item || typeof item !== "object") { + throw new Error(`Invalid task-batch payload: item[${index}] must be an object`); + } + for (const field of ["taskId", "taskTitle", "status", "repository", "workspace"]) { + if (typeof item[field] !== "string" || item[field].trim() === "") { + throw new Error( + `Invalid task-batch payload: item[${index}].${field} must be a non-empty string`, + ); + } + } + return { + ...item, + taskId: item.taskId.trim(), + taskTitle: item.taskTitle.trim(), + status: item.status.trim(), + repository: item.repository.trim(), + workspace: item.workspace.trim(), + branch: + typeof item.branch === "string" + ? item.branch.slice(0, FIELD_MAX_LEN) + : item.branch ?? null, + scope: + typeof item.scope === "string" + ? item.scope.slice(0, FIELD_MAX_LEN) + : item.scope ?? null, + }; + }); +} + +/** + * Build a concise log-friendly summary of a task-batch payload array. + * @param {unknown[]} items + * @returns {{ type: "array", count: number, taskIds: string[] }} + */ +export function summarizeTaskBatchPayloadForLog(items) { + const arr = Array.isArray(items) ? items : []; + return { + type: "array", + count: arr.length, + taskIds: arr + .map((item) => (typeof item?.taskId === "string" ? item.taskId.trim() : "")) + .filter(Boolean), + }; +} From 99033eead85187021176e71834568c886fdbf398 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Mar 2026 14:30:11 +0000 Subject: [PATCH 5/5] fix: use resolved path consistently in cliCreateBatch Agent-Logs-Url: https://github.com/virtengine/bosun/sessions/2bb55cd8-1fc6-4019-8053-6cff534cb72e Co-authored-by: jaeko44 <9289791+jaeko44@users.noreply.github.com> --- task/task-cli.mjs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/task/task-cli.mjs b/task/task-cli.mjs index 4e110606..9dd56d35 100644 --- a/task/task-cli.mjs +++ b/task/task-cli.mjs @@ -1728,13 +1728,14 @@ async function cliCreateBatch(args) { process.exit(1); } - if (!existsSync(resolve(payloadFile))) { + const resolvedPayloadFile = resolve(payloadFile); + if (!existsSync(resolvedPayloadFile)) { console.error(` Error: file not found: ${payloadFile}`); process.exit(1); } try { - const result = await taskImport(payloadFile); + const result = await taskImport(resolvedPayloadFile); console.log(JSON.stringify(result, null, 2)); } catch (err) { console.error(` Error: ${err.message}`);