AskUserQuestion silently returns empty answers when called inside plugin skills

Resolved 💬 12 comments Opened Feb 28, 2026 by MickaelV0 Closed Mar 2, 2026

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

  1. Start Claude Code with --dangerously-skip-permissions
  1. 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
``

  1. The skill declares AskUserQuestion in its allowed-tools:

``yaml
---
name: compress
allowed-tools: Read, Write, Edit, Glob, Grep, AskUserQuestion
---
``

  1. Invoke the skill: /compress:compress some-file
  1. 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.
``

  1. In the same session, call AskUserQuestion directly (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

  1. The Skill tool's contextModifier injects the skill's allowed-tools into alwaysAllowRules.command:

``javascript
contextModifier(context) {
// Wraps getAppState() to inject allowedTools into:
// toolPermissionContext.alwaysAllowRules.command
// This includes "AskUserQuestion" from the skill's allowed-tools
}
``

  1. When AskUserQuestion is called within the skill, Xv9 runs:
  • $0B collects rules from all sources including "command" (via c2H/clA)
  • llA matches: rule.ruleValue.toolName === "AskUserQuestion"true
  • Returns { behavior: "allow" } immediately with the original input (empty answers: {})
  • requiresUserInteraction() at Step 5 is never reached
  • User never sees the prompt
  1. The AskUserQuestion tool 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-permissions mode. Fixed in v2.0.28 by adding the requiresUserInteraction() guard at Step 5. That fix works for direct calls but doesn't cover the alwaysAllowRules early return at Step 1.
  • #14956 — Broader allowed-tools issues

Plugin repo for reproduction

https://github.com/Roxabi/roxabi-plugins — both compress and 1b1 skills trigger this bug.

View original on GitHub ↗

This issue has 12 comments on GitHub. Read the full discussion on GitHub ↗