fix(workflow): add plain-text fallback for AskUserQuestion on non-Claude runtimes (#2042)

AskUserQuestion is a Claude Code-only tool. When running GSD on OpenAI Codex,
Gemini CLI, or other non-Claude runtimes, the model renders the tool call as a
markdown code block instead of executing it, so the interactive TUI never
appears and the session stalls without collecting user input.

The workflow.text_mode / --text flag mechanism already handles this in 5 of
the 37 affected workflows. This commit adds the same TEXT_MODE fallback
instruction to all remaining 32 workflows so that, when text_mode is enabled,
every AskUserQuestion call is replaced with a plain-text numbered list that
any runtime can handle.

Fixes #2012

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-04-10 12:30:46 -04:00
committed by GitHub
parent c8ab20b0a6
commit 47badff2ee
33 changed files with 152 additions and 0 deletions

View File

@@ -108,6 +108,9 @@ Read each file to verify classification. Don't classify based on filename alone.
<step name="present_classification">
Present the classification to the user for confirmation before proceeding:
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
```
AskUserQuestion(
header: "Test Classification",

View File

@@ -70,6 +70,8 @@ If potential duplicate found:
1. Read the existing todo
2. Compare scope
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
If overlapping, use AskUserQuestion:
- header: "Duplicate?"
- question: "Similar todo exists: [title]. What would you like to do?"

View File

@@ -75,6 +75,8 @@ Continue (non-blocking).
AI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-AI-SPEC.md 2>/dev/null | head -1)
```
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
**If exists:** Use AskUserQuestion:
- header: "Existing AI-SPEC"
- question: "AI-SPEC.md already exists for Phase {N}. What would you like to do?"

View File

@@ -412,6 +412,8 @@ Proceed to iterate step.
Read the human_verification section from VERIFICATION.md to get the count and items requiring manual testing.
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Display the items, then ask user via AskUserQuestion:
- **question:** "Phase ${PHASE_NUM} has items needing manual verification. Validate now or continue to next phase?"
- **options:** "Validate now" / "Continue without validation"

View File

@@ -102,6 +102,8 @@ If `.planning/ROADMAP.md` exists:
<step name="offer_actions">
**If todo maps to a roadmap phase:**
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Use AskUserQuestion:
- header: "Action"
- question: "This todo relates to Phase [N]: [name]. What would you like to do?"

View File

@@ -93,6 +93,8 @@ No phase directories found to archive. Phases may have been removed or archived
Stop here.
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
AskUserQuestion: "Proceed with archiving?" with options: "Yes — archive listed phases" | "Cancel"
If "Cancel": Stop.

View File

@@ -387,6 +387,8 @@ Verify: `✅ Milestone archived to .planning/milestones/`
**Phase archival (optional):** After archival completes, ask the user:
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
AskUserQuestion(header="Archive Phases", question="Archive phase directories to milestones/?", options: "Yes — move to milestones/v[X.Y]-phases/" | "Skip — keep phases in place")
If "Yes": move phase directories to the milestone archive:

View File

@@ -214,6 +214,8 @@ Write `.planning/phases/XX-name/DISCOVERY.md`:
After creating DISCOVERY.md, check confidence level.
If confidence is LOW:
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Use AskUserQuestion:
- header: "Low Conf."

View File

@@ -11,6 +11,8 @@ Read all files referenced by the invoking prompt's execution_context before star
<step name="validate">
**Check for input.**
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
If `$ARGUMENTS` is empty, ask via AskUserQuestion:
```

View File

@@ -84,6 +84,8 @@ Assemble the complete doc queue from always-on docs plus conditional docs from c
If CONTRIBUTING.md is in the conditional queue AND does NOT appear in the `existing_docs` array from init JSON:
1. If `--force` is present in `$ARGUMENTS`: skip this check, include CONTRIBUTING.md in the queue.
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
2. Otherwise, use AskUserQuestion to confirm:
```

View File

@@ -42,6 +42,8 @@ EVAL_REVIEW_FILE=$(ls "${PHASE_DIR}"/*-EVAL-REVIEW.md 2>/dev/null | head -1)
**State B** — SUMMARY.md exists, no AI-SPEC.md: Audit against general best practices
**State C** — No SUMMARY.md: Exit — "Phase {N} not executed. Run /gsd-execute-phase {N} first."
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
**If `EVAL_REVIEW_FILE` non-empty:** Use AskUserQuestion:
- header: "Existing Eval Review"
- question: "EVAL-REVIEW.md already exists for Phase {N}."

View File

@@ -135,6 +135,8 @@ This IS the execution instructions. Follow exactly. If plan references CONTEXT.m
node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" phases list --type summaries --raw
# Extract the second-to-last summary from the JSON result
```
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
If previous SUMMARY has unresolved "Issues Encountered" or "Next Phase Readiness" blockers: AskUserQuestion(header="Previous Issues", options: "Proceed anyway" | "Address first" | "Review previous").
</step>

View File

@@ -164,6 +164,8 @@ Exit WITHOUT writing any files. This is the safety gate — no PLAN.md is writte
**If only WARNINGS and/or INFO (no blockers):**
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Ask via AskUserQuestion using the approve-revise-abort pattern:
- question: "Review the warnings above. Proceed with import?"
- header: "Approve?"

View File

@@ -326,6 +326,9 @@ gh pr close {number} --comment "Closed by GSD inbox triage: this PR does not mee
```
Always confirm with the user before closing anything:
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
```
AskUserQuestion:
question: "Found {N} items to close. Review the list above — proceed with closing?"

View File

@@ -117,6 +117,8 @@ All {phase_count} phases done. Ready for final steps:
→ /gsd-complete-milestone — archive and wrap up
```
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Ask user via AskUserQuestion:
- **question:** "All phases complete. What next?"
- **options:** "Verify work" / "Complete milestone" / "Exit manager"

View File

@@ -40,6 +40,8 @@ If the flag is absent, keep the current behavior of continuing phase numbering f
**If no context file:**
- Present what shipped in last milestone
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
- Ask inline (freeform, NOT AskUserQuestion): "What do you want to build next?"
- Wait for their response, then use AskUserQuestion to probe specifics
- If user selects "Other" at any point to provide freeform input, ask follow-up as plain text — not another AskUserQuestion

View File

@@ -103,6 +103,8 @@ git init
**If `needs_codebase_map` is true** (from init — existing code detected but no codebase map):
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Use AskUserQuestion:
- header: "Codebase"

View File

@@ -31,6 +31,8 @@ Extract from $ARGUMENTS:
**If `--name` is missing and not `--auto`:**
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Use AskUserQuestion:
- header: "Workspace Name"
- question: "What should this workspace be called?"

View File

@@ -1,5 +1,7 @@
<purpose>
Zero-friction idea capture. One Write call, one confirmation line. No questions, no prompts.
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Runs inline — no Task, no AskUserQuestion, no Bash.
</purpose>

View File

@@ -32,6 +32,9 @@ mkdir -p .planning/seeds
<step name="gather_context">
Ask focused questions to build a complete seed:
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
```
AskUserQuestion(
header: "Trigger",

View File

@@ -30,6 +30,8 @@ PROFILE_PATH="$HOME/.claude/get-shit-done/USER-PROFILE.md"
**If profile exists AND --refresh NOT set AND --questionnaire NOT set:**
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Use AskUserQuestion:
- header: "Existing Profile"
- question: "You already have a profile. What would you like to do?"

View File

@@ -38,6 +38,9 @@ Parse `$ARGUMENTS` for:
If `$DESCRIPTION` is empty after parsing, prompt user interactively:
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
```
AskUserQuestion(
header: "Quick Task",

View File

@@ -23,6 +23,8 @@ Parse JSON for: `workspace_name`, `workspace_path`, `has_manifest`, `strategy`,
First run `/gsd-list-workspaces` to show available workspaces, then ask:
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Use AskUserQuestion:
- header: "Remove Workspace"
- question: "Which workspace do you want to remove?"

View File

@@ -73,6 +73,8 @@ If `threats_open: 0` → skip to Step 6 directly.
## 4. Present Threat Plan
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Call AskUserQuestion with threat table and options:
1. "Verify all open threats" → Step 5
2. "Accept all open — document in accepted risks log" → add to SECURITY.md accepted risks, set all CLOSED, Step 6

View File

@@ -39,6 +39,8 @@ Parse current values (default to `true` if not present):
</step>
<step name="present_settings">
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Use AskUserQuestion with current values pre-selected:
```

View File

@@ -161,6 +161,9 @@ Report: "PR #{number} created: {url}"
<step name="optional_review">
Ask if user wants to trigger a code review:
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
```
AskUserQuestion:
question: "PR created. Run a code review before merge?"

View File

@@ -83,6 +83,8 @@ Continue (non-blocking).
UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
```
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
**If exists:** Use AskUserQuestion:
- header: "Existing UI-SPEC"
- question: "UI-SPEC.md already exists for Phase {N}. What would you like to do?"

View File

@@ -44,6 +44,8 @@ UI_REVIEW_FILE=$(ls "${PHASE_DIR}"/*-UI-REVIEW.md 2>/dev/null | head -1)
**If `SUMMARY_FILES` empty:** Exit — "Phase {N} not executed. Run /gsd-execute-phase {N} first."
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
**If `UI_REVIEW_FILE` non-empty:** Use AskUserQuestion:
- header: "Existing UI Review"
- question: "UI-REVIEW.md already exists for Phase {N}."

View File

@@ -63,6 +63,8 @@ Recent GSD commits:
3. ghi9012 fix(02-03): correct validation logic
```
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Use AskUserQuestion to ask:
- question: "Which commits to revert? Enter numbers (e.g., 1,3) or 'all'"
- header: "Select"

View File

@@ -341,6 +341,8 @@ Your custom files in other locations are preserved:
If you've modified any GSD files directly, they'll be automatically backed up to `gsd-local-patches/` and can be reapplied with `/gsd-reapply-patches` after the update.
```
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Use AskUserQuestion:
- Question: "Proceed with update?"
- Options:

View File

@@ -83,6 +83,8 @@ No gaps → skip to Step 6, set `nyquist_compliant: true`.
## 4. Present Gap Plan
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Call AskUserQuestion with gap table and options:
1. "Fix all gaps" → Step 5
2. "Skip — mark manual-only" → add to Manual-Only, Step 6

View File

@@ -248,6 +248,8 @@ Display the returned checkpoint EXACTLY as-is:
- Do NOT add commentary before or after the block.
- If you notice protocol/meta markers such as `to=all:`, role-routing text, XML system tags, hidden instruction markers, ad copy, or any unrelated suffix, discard the draft and output `{CHECKPOINT}` only.
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Wait for user response (plain text, no AskUserQuestion).
</step>

View File

@@ -0,0 +1,83 @@
/**
* Regression guard for #2012: AskUserQuestion is Claude Code-only — non-Claude
* runtimes (OpenAI Codex, Gemini, etc.) render it as a markdown code block
* instead of triggering the interactive TUI, so the session stalls.
*
* Every workflow that calls AskUserQuestion MUST include a TEXT_MODE fallback
* instruction so that, when `workflow.text_mode` is true (or `--text` is
* passed), all AskUserQuestion calls are replaced with plain-text numbered
* lists that any runtime can handle.
*
* The canonical fallback phrase is:
* "TEXT_MODE" (or "text_mode") paired with "plain-text" (or "plain text")
* near the first AskUserQuestion reference in the file.
*/
'use strict';
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const ROOT = path.join(__dirname, '..');
const WORKFLOWS_DIR = path.join(ROOT, 'get-shit-done', 'workflows');
/**
* Return true if the file content contains a TEXT_MODE / text_mode fallback
* instruction for AskUserQuestion calls.
*
* Acceptable forms (case-insensitive on key terms):
* - "TEXT_MODE" + "plain-text" or "plain text"
* - "text_mode" + "plain-text" or "plain text"
* - "text mode" + "plain-text" or "plain text"
*/
function hasTextModeFallback(content) {
const lower = content.toLowerCase();
const hasTextMode =
lower.includes('text_mode') ||
lower.includes('text mode');
const hasPlainText =
lower.includes('plain-text') ||
lower.includes('plain text') ||
lower.includes('numbered list');
return hasTextMode && hasPlainText;
}
describe('AskUserQuestion text-mode fallback (#2012)', () => {
test('every workflow that uses AskUserQuestion includes a TEXT_MODE plain-text fallback', () => {
const violations = [];
const files = fs.readdirSync(WORKFLOWS_DIR).filter(f => f.endsWith('.md'));
for (const fname of files) {
const fpath = path.join(WORKFLOWS_DIR, fname);
const content = fs.readFileSync(fpath, 'utf-8');
if (!content.includes('AskUserQuestion')) continue;
if (!hasTextModeFallback(content)) {
violations.push(fname);
}
}
assert.strictEqual(
violations.length,
0,
[
'AskUserQuestion is Claude Code-only (issue #2012).',
'Every workflow that uses AskUserQuestion must include a TEXT_MODE fallback',
'so non-Claude runtimes (OpenAI Codex, Gemini, etc.) can present questions',
'as plain-text numbered lists instead of stalling on an unexecuted tool call.',
'',
'Add this near the argument-parsing section of each workflow:',
' Set TEXT_MODE=true if --text is present in $ARGUMENTS OR text_mode from',
' init JSON is true. When TEXT_MODE is active, replace every AskUserQuestion',
' call with a plain-text numbered list and ask the user to type their choice',
' number.',
'',
'Workflows missing the fallback:',
...violations.map(v => ' get-shit-done/workflows/' + v),
].join('\n')
);
});
});