AskUserQuestion silently returns empty answers when called inside plugin skills
Bug Description
AskUserQuestion silently auto-completes with empty answers when called inside a skill loaded from a plugin via the Skill tool. The user never sees the interactive prompt UI. The same AskUserQuestion call works perfectly outside the skill context in the same session.
Environment
- Claude Code version: 2.1.63
- OS: Linux (WSL2) 6.6.87.2-microsoft-standard-WSL2
- Permission mode:
--dangerously-skip-permissions
Steps to Reproduce
- Start Claude Code with
--dangerously-skip-permissions
- Install a plugin that contains a skill using
AskUserQuestion. For example, from Roxabi/roxabi-plugins:
````
claude plugin marketplace add Roxabi/roxabi-plugins
claude plugin install compress
- The skill declares
AskUserQuestionin itsallowed-tools:
``yaml``
---
name: compress
allowed-tools: Read, Write, Edit, Glob, Grep, AskUserQuestion
---
- Invoke the skill:
/compress:compress some-file
- When the skill calls
AskUserQuestion, the user is never prompted. The tool returns immediately with an empty answer:
````
User has answered your questions: . You can now continue with the user's answers in mind.
- In the same session, call
AskUserQuestiondirectly (outside any skill context) — it works correctly, shows the prompt UI, and returns the user's selection.
Reproduction with multiple skills
Reproduced with two different skills from the same plugin repo (compress and 1b1), both declaring AskUserQuestion in allowed-tools. The behavior is consistent — AskUserQuestion never renders inside a plugin skill context.
Expected Behavior
AskUserQuestion should display the interactive prompt UI and wait for user input when called inside a skill, even in --dangerously-skip-permissions mode.
Actual Behavior
AskUserQuestion returns immediately with empty/no answers. The user never sees the prompt. The skill proceeds as if the user responded, but with no actual input.
Root Cause
We traced through the minified v2.1.63 source and found the exact code path.
The permission evaluator (Xv9) has an early return that bypasses requiresUserInteraction()
async function Xv9(tool, input, ...) {
let appState = await toolUseContext.getAppState();
// STEP 1: Check alwaysAllowRules (includes skill allowed-tools via "command" source)
let allowMatch = $0B(appState.toolPermissionContext, tool);
if (allowMatch) {
return { behavior: "allow", updatedInput: input, ... };
// ^^^ RETURNS HERE — everything below is skipped
}
// STEP 2: Check deny rules
// STEP 3: Check ask rules
// STEP 4: Call tool's own checkPermissions()
let permResult = await tool.checkPermissions(input, toolUseContext);
// STEP 5: requiresUserInteraction guard — NEVER REACHED when in skill context
if (tool.requiresUserInteraction?.() && permResult?.behavior === "ask") {
return permResult; // This would force the UI prompt
}
// STEP 6: bypassPermissions mode, etc.
}
How the skill triggers the bug
- The Skill tool's
contextModifierinjects the skill'sallowed-toolsintoalwaysAllowRules.command:
``javascript``
contextModifier(context) {
// Wraps getAppState() to inject allowedTools into:
// toolPermissionContext.alwaysAllowRules.command
// This includes "AskUserQuestion" from the skill's allowed-tools
}
- When
AskUserQuestionis called within the skill,Xv9runs:
$0Bcollects rules from all sources including"command"(viac2H/clA)llAmatches:rule.ruleValue.toolName === "AskUserQuestion"→true- Returns
{ behavior: "allow" }immediately with the original input (emptyanswers: {}) requiresUserInteraction()at Step 5 is never reached- User never sees the prompt
- The
AskUserQuestiontool receives the auto-approved input with empty answers and returns:
````
"User has answered your questions: ."
Why it works outside skill context
Without an active skill, alwaysAllowRules.command doesn't contain "AskUserQuestion". Step 1 doesn't match, so the flow reaches Steps 4–5 where checkPermissions() returns "ask" and requiresUserInteraction() preserves it → user gets prompted.
The hook path already handles this correctly
The hook approval path has the proper guard:
if (hookApproved && !tool.requiresUserInteraction?.()) → skip permission check
if (hookApproved && tool.requiresUserInteraction?.()) → still run permission check
The alwaysAllowRules path in Xv9 lacks the same guard.
Suggested Fix
Add a requiresUserInteraction() check to the alwaysAllowRules early return in Xv9:
let allowMatch = $0B(appState.toolPermissionContext, tool);
if (allowMatch && !tool.requiresUserInteraction?.()) {
return { behavior: "allow", updatedInput: input, ... };
}
This matches the pattern already used in the hook approval path and ensures AskUserQuestion (and any future tools declaring requiresUserInteraction) always reaches the UI prompt regardless of how the permission was granted.
Related Issues
- #9846 — Original bug with same symptom in
--dangerously-skip-permissionsmode. Fixed in v2.0.28 by adding therequiresUserInteraction()guard at Step 5. That fix works for direct calls but doesn't cover thealwaysAllowRulesearly return at Step 1. - #14956 — Broader
allowed-toolsissues
Plugin repo for reproduction
https://github.com/Roxabi/roxabi-plugins — both compress and 1b1 skills trigger this bug.
12 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
I have the same issue.
Workaround
Removing
AskUserQuestionfrom the skill'sallowed-toolsfrontmatter prevents it from being injected intoalwaysAllowRules.command, so the tool goes through the normal permission path whererequiresUserInteraction()is checked and the UI prompt renders correctly.Commit: https://github.com/Roxabi/roxabi-plugins/commit/3cabe10
I'm using the same workaround you mentioned.
Root Cause: PreToolUse Hooks with Wildcard Matchers Corrupt AskUserQuestion Input
This is the same underlying bug as #29530. I've tracked it down and published a fix.
Why It Happens
If you have any PreToolUse hook with:
updatedInputwith extra properties (e.g., environment variables)...then
AskUserQuestion's input schema gets corrupted. Claude Code replaces the tool input wholesale with the hook'supdatedInput, and the extra keys (env, etc.) cause the interactive UI to silently fail.Why "Inside Plugin Skills" Specifically
The workaround in #29547 comment (removing
AskUserQuestionfromallowed-tools) works because it changes the permission path. WhenAskUserQuestionis inallowed-tools, it goes throughalwaysAllowRules→ PreToolUse hooks fire → input gets corrupted. When it's NOT inallowed-tools, it hits the normal permission check whererequiresUserInteraction()bypasses hooks.But this is a workaround, not a fix — the real issue is that hooks shouldn't corrupt tool input.
The Fix (For Hook Authors)
In your PreToolUse hook, skip UI tools entirely — don't return
updatedInputfor them:Suggestion for Claude Code Core
Claude Code should validate
updatedInputagainst the tool's parameter schema before applying it. Unknown properties should be stripped or warned about. This would prevent hooks from accidentally corrupting any tool's input.Fix Published
terrylica/cc-skills v11.63.1 — the
pretooluse-subprocess-stdin-inlet-guard.tshook now uses aPASSTHROUGH_TOOLSset.Related: #29530, #13439, #10400
having same when using GSD , from its discussion agent skill.
Confirmed reproduction — Claude Code v2.1.63, Linux VPS, GSD skills
Environment:
--dangerously-skip-permissionsBash, not*)Reproduction:
Calling
AskUserQuestionfrom inside a GSD workflow skill (invoked via theSkilltool). The tool returns immediately with empty answers:The user never sees any prompt UI. The skill proceeds with no input.
Why the hook-corruption path (described in the last comment) doesn't apply here:
Our PreToolUse hooks use specific matchers (
Bashonly) and do not returnupdatedInput. Despite this, the bug still reproduces — which points to the originalalwaysAllowRulesearly return described in the issue body, not the hook mutation path.Context that may help:
The skill is invoked via the
Skilltool (not a slash command directly). The GSD workflow skill callsAskUserQuestionmid-execution to gather user input (e.g. milestone scope selection). TheSkilltool'scontextModifierinjects the skill'sallowed-toolsintoalwaysAllowRules.command, which triggers the early return beforerequiresUserInteraction()is checked.This is a regression — #9846 was closed as fixed in v2.0.28 but the fix only covered the
bypassPermissionspath, not thealwaysAllowRulespath introduced by skillallowed-toolsinjection.+1 — Hitting this exact bug on WSL2 (Linux 6.6.87.2-microsoft-standard-WSL2), Claude Code v2.1.63 with Opus 4.6.
Both our
/brainstormand/debug-appskills declareAskUserQuestioninallowed-tools. The tool auto-completes with empty answers — user never sees the prompt UI.Workaround: Removing
AskUserQuestionfromallowed-toolsin the skill frontmatter. The tool still works (Claude gets prompted for permission each time), and the UI renders correctly because it goes throughcheckPermissions()→requiresUserInteraction()instead of thealwaysAllowRulesearly return.The root cause analysis in this issue is spot-on. The suggested one-line fix (
allowMatch && !tool.requiresUserInteraction?.()) would resolve this cleanly.This will be fixed in an upcoming release.
still seeing it in Claude Code v2.1.68 on windows
still seeing it in Claude Code v2.1.69 on macOs
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.