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.
This issue has 12 comments on GitHub. Read the full discussion on GitHub ↗