mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2026-01-30 06:12:05 +00:00
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 <Crunchyman-ralph@users.noreply.github.com>
This commit is contained in:
committed by
Ralph Khreish
parent
0729635fe4
commit
226678b93a
7
.changeset/fix-codex-cli-schema.md
Normal file
7
.changeset/fix-codex-cli-schema.md
Normal file
@@ -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.
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user