From 226678b93aa01d0e62c0fac852802e9955c7ebd7 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Mon, 29 Dec 2025 08:50:15 +0000 Subject: [PATCH] fix: remove .default() from Zod schemas for OpenAI strict JSON schema validation Fixes #1552 OpenAI's structured outputs API requires all properties to be in the 'required' array of JSON Schema. Zod's .default() makes fields optional, causing codex-cli provider to fail with 'Missing dependencies' error. Changes: - Removed .default() from SubtaskSchema, BaseTaskSchema, UpdatedTaskSchema - Added application-level default handling in expand-task, update-task-by-id, update-tasks, and parse-prd-streaming - Ensures all schema properties are marked as required for OpenAI compatibility - Maintains backward compatibility by applying defaults when AI doesn't provide values Co-authored-by: Ralph Khreish --- .changeset/fix-codex-cli-schema.md | 7 +++++++ scripts/modules/task-manager/expand-task.js | 7 ++++++- .../parse-prd/parse-prd-streaming.js | 11 +++++++++++ .../modules/task-manager/update-task-by-id.js | 11 +++++++++-- scripts/modules/task-manager/update-tasks.js | 18 +++++++++++++++++- src/schemas/base-schemas.js | 15 +++++++-------- src/schemas/update-tasks.js | 2 +- 7 files changed, 58 insertions(+), 13 deletions(-) create mode 100644 .changeset/fix-codex-cli-schema.md diff --git a/.changeset/fix-codex-cli-schema.md b/.changeset/fix-codex-cli-schema.md new file mode 100644 index 00000000..e752bdd8 --- /dev/null +++ b/.changeset/fix-codex-cli-schema.md @@ -0,0 +1,7 @@ +--- +"task-master-ai": patch +--- + +fix: Remove .default() from Zod schemas to satisfy OpenAI strict JSON schema validation + +This fixes an issue where codex-cli provider (using OpenAI API) would fail with "Missing 'dependencies'" error during task expansion. OpenAI's structured outputs require all properties to be in the 'required' array, but Zod's .default() makes fields optional. The fix removes .default() from schemas and applies defaults at the application level instead. diff --git a/scripts/modules/task-manager/expand-task.js b/scripts/modules/task-manager/expand-task.js index cdae1d6f..4bb22ebe 100644 --- a/scripts/modules/task-manager/expand-task.js +++ b/scripts/modules/task-manager/expand-task.js @@ -331,7 +331,12 @@ async function expandTask( if (!mainResult || !Array.isArray(mainResult.subtasks)) { throw new Error('AI response did not include a valid subtasks array.'); } - generatedSubtasks = mainResult.subtasks; + generatedSubtasks = mainResult.subtasks.map((subtask) => ({ + ...subtask, + dependencies: subtask.dependencies ?? [], + status: subtask.status ?? 'pending', + testStrategy: subtask.testStrategy ?? null + })); logger.info(`Received ${generatedSubtasks.length} subtasks from AI.`); } catch (error) { if (loadingIndicator) stopLoadingIndicator(loadingIndicator); diff --git a/scripts/modules/task-manager/parse-prd/parse-prd-streaming.js b/scripts/modules/task-manager/parse-prd/parse-prd-streaming.js index cbd7c718..eed32379 100644 --- a/scripts/modules/task-manager/parse-prd/parse-prd-streaming.js +++ b/scripts/modules/task-manager/parse-prd/parse-prd-streaming.js @@ -570,6 +570,17 @@ async function processWithGenerateObject(context, logger) { // Extract tasks from the result (handle both direct tasks and mainResult.tasks) const tasks = result?.mainResult || result; + // Apply defaults to ensure all required fields are present + if (tasks && Array.isArray(tasks.tasks)) { + tasks.tasks = tasks.tasks.map((task) => ({ + ...task, + dependencies: task.dependencies ?? [], + priority: task.priority ?? null, + details: task.details ?? null, + testStrategy: task.testStrategy ?? null + })); + } + // Process the generated tasks if (tasks && Array.isArray(tasks.tasks)) { // Update progress tracker with final tasks diff --git a/scripts/modules/task-manager/update-task-by-id.js b/scripts/modules/task-manager/update-task-by-id.js index bd08b84e..b4616603 100644 --- a/scripts/modules/task-manager/update-task-by-id.js +++ b/scripts/modules/task-manager/update-task-by-id.js @@ -422,7 +422,13 @@ async function updateTaskById( } // Full update mode: Use structured data directly - const updatedTask = aiServiceResponse.mainResult.task; + const updatedTask = { + ...aiServiceResponse.mainResult.task, + dependencies: aiServiceResponse.mainResult.task.dependencies ?? [], + priority: aiServiceResponse.mainResult.task.priority ?? null, + details: aiServiceResponse.mainResult.task.details ?? null, + testStrategy: aiServiceResponse.mainResult.task.testStrategy ?? null + }; // --- Task Validation/Correction (Keep existing logic) --- if (!updatedTask || typeof updatedTask !== 'object') @@ -465,7 +471,8 @@ async function updateTaskById( depId < currentSubtaskId ) : [], - status: subtask.status || 'pending' + status: subtask.status || 'pending', + testStrategy: subtask.testStrategy ?? null }; currentSubtaskId++; return correctedSubtask; diff --git a/scripts/modules/task-manager/update-tasks.js b/scripts/modules/task-manager/update-tasks.js index 65c57790..c00c8055 100644 --- a/scripts/modules/task-manager/update-tasks.js +++ b/scripts/modules/task-manager/update-tasks.js @@ -228,7 +228,23 @@ async function updateTasks( stopLoadingIndicator(loadingIndicator, 'AI update complete.'); // With generateObject, we get structured data directly - const parsedUpdatedTasks = aiServiceResponse.mainResult.tasks; + const parsedUpdatedTasks = aiServiceResponse.mainResult.tasks.map( + (task) => ({ + ...task, + dependencies: task.dependencies ?? [], + priority: task.priority ?? null, + details: task.details ?? null, + testStrategy: task.testStrategy ?? null, + subtasks: task.subtasks + ? task.subtasks.map((subtask) => ({ + ...subtask, + dependencies: subtask.dependencies ?? [], + status: subtask.status ?? 'pending', + testStrategy: subtask.testStrategy ?? null + })) + : null + }) + ); // --- Update Tasks Data (Updated writeJSON call) --- if (!Array.isArray(parsedUpdatedTasks)) { diff --git a/src/schemas/base-schemas.js b/src/schemas/base-schemas.js index f6777da5..9335e22c 100644 --- a/src/schemas/base-schemas.js +++ b/src/schemas/base-schemas.js @@ -26,13 +26,12 @@ export const BaseTaskSchema = z title: z.string().min(1).max(200), description: z.string().min(1), status: TaskStatusSchema, - dependencies: z.array(z.union([z.number().int(), z.string()])).default([]), + dependencies: z.array(z.union([z.number().int(), z.string()])), priority: z .enum(['low', 'medium', 'high', 'critical']) - .nullable() - .default(null), - details: z.string().nullable().default(null), - testStrategy: z.string().nullable().default(null) + .nullable(), + details: z.string().nullable(), + testStrategy: z.string().nullable() }) .strict(); @@ -41,9 +40,9 @@ export const SubtaskSchema = z id: z.number().int().positive(), title: z.string().min(5).max(200), description: z.string().min(10), - dependencies: z.array(z.number().int()).default([]), + dependencies: z.array(z.number().int()), details: z.string().min(20), - status: z.enum(['pending', 'done', 'completed']).default('pending'), - testStrategy: z.string().nullable().default(null) + status: z.enum(['pending', 'done', 'completed']), + testStrategy: z.string().nullable() }) .strict(); diff --git a/src/schemas/update-tasks.js b/src/schemas/update-tasks.js index 61d93524..63079903 100644 --- a/src/schemas/update-tasks.js +++ b/src/schemas/update-tasks.js @@ -2,7 +2,7 @@ import { z } from 'zod'; import { BaseTaskSchema, SubtaskSchema } from './base-schemas.js'; export const UpdatedTaskSchema = BaseTaskSchema.extend({ - subtasks: z.array(SubtaskSchema).nullable().default(null) + subtasks: z.array(SubtaskSchema).nullable() }).strict(); export const UpdateTasksResponseSchema = z