Yeachan-Heo/oh-my-codex · error · Error

Structured question returned an invalid next-step answer.

Error message

Structured question returned an invalid next-step answer.

What it means

After extracting a structured answer for the next-step prompt, promptAction() only accepts the lowercased values 'launch' or 'refine'. Any other string (or non-string answer.value) is invalid and throws before falling back to interactive input.

Source

Thrown at src/cli/autoresearch-guided.ts:142

					value: "launch",
					description: "Finalize artifacts and hand off to $autoresearch.",
				},
				{
					label: "Refine further",
					value: "refine",
					description: "Keep clarifying before launch.",
				},
			],
			allow_other: false,
			source: "deep-interview",
		});
		const answer = primaryStructuredAnswer(response);
		const answerValue = typeof answer.value === "string"
			? answer.value.trim().toLowerCase()
			: "";
		if (answerValue === "launch") return "launch";
		if (answerValue === "refine") return "refine";
		throw new Error('Structured question returned an invalid next-step answer.');
	}

	const answer = (
		await io.question(
			`\nNext step [launch/refine further] (default: ${launchReady ? "launch" : "refine further"})\n> `,
		)
	)
		.trim()
		.toLowerCase();
	if (!answer) return launchReady ? "launch" : "refine";
	if (answer === "launch") return "launch";
	if (answer === "refine further" || answer === "refine" || answer === "r")
		return "refine";
	throw new Error('Please choose either "launch" or "refine further".');
}

function createStructuredQuestionAsker(
	repoRoot: string,

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Make the structured asker return exactly 'launch' or 'refine' as answer.value
  2. If the answerer is an LLM prompt, constrain it (enum/single-choice) so it emits only those two tokens
  3. Catch this error and fall back to interactive io.question (shouldFallbackFromStructuredQuestion-style logic)

Example fix

// before
answer: { value: 'launch when ready' }
// after
answer: { value: 'launch' }
Defensive patterns

Strategy: fallback

Validate before calling

const val = String(answer?.value ?? '').trim().toLowerCase();
if (val !== 'launch' && val !== 'refine') {
  // fall back to interactive prompt instead of throwing
  return await io.question('Next step [launch/refine further]\n> ');
}

Type guard

const isValidAction = (v: unknown): v is 'launch'|'refine' =>
  typeof v === 'string' && ['launch','refine'].includes(v.trim().toLowerCase());

Try / catch

try { return await promptActionStructured(resp); } catch (e) { if (e.message.includes('invalid next-step answer')) return await promptActionInteractive(io); throw e; }

Prevention

When it happens

Trigger: The structured asker returns an answer whose trimmed/lowercased value is something else — e.g. 'launch!', 'yes', 'L', or answer.value being a number/object (typeof !== 'string' yields '').

Common situations: LLM-backed answerers returning free-form text like 'let us launch now', multi-select payloads, or non-string value types from a changed schema.

Related errors


AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27). Data as JSON: /api/errors/927966bedda3bebf. Report an issue: GitHub.