Skip to content

Task tool ignores per-target deny and allows self-dispatch recursion #11324

Closed

Description

@HyunggyuJang

Summary

Task tool ignores per-target deny rules and allows an agent to dispatch itself. This enables recursive subagent spawning (self-dispatch loop) even when permission.task denies that target.

Steps to reproduce

  1. Define an agent sre with task permission allowing * but denying sre as a target.
# opencode.json (agent snippet)
permission:
  task:
    "*": allow
    sre: deny
  1. Start a session as @sre.
  2. Invoke the Task tool with subagent_type: "sre".

Example payload:

{
  "tool": "task",
  "arguments": {
    "subagent_type": "sre",
    "prompt": "...",
    "description": "..."
  }
}

Expected

The Task tool call is rejected with a permission error because the target sre is denied.

Actual

The Task tool call succeeds and spawns @sre again, enabling recursive self-dispatch loops.

Impact

  • Deny rules for specific subagent targets are not enforced at execution time.
  • Self-dispatch loops can create runaway subagent chains.

Workaround

Add a custom guard in tool.execute.before to reject Task calls when the current agent is sre and subagent_type is sre.

Environment

  • Observed on macOS with current anomalyco/opencode main (date: 2026-01-31)
  • Configured via agent permissions (see snippet above)

Activity

  1. github-actions commented on Jan 30, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    The core issue appears to be that the task tool systematically ignores configured permission rules. The most relevant are #7474 (nearly identical - ignoring per-target permissions) and #6527 (same root cause - restrictions ignored).

    Feel free to ignore if your specific case isn't addressed by these issues.

  2. HyunggyuJang commented on Jan 30, 2026

    @HyunggyuJang
    Author

    I inspected packages/opencode/src/tool/task.ts (lines ~23-60). Root cause: per-target deny is only used to filter the tool description (accessibleAgents). The execute path does not enforce it and proceeds to Agent.get even when PermissionNext.evaluate("task", params.subagent_type, caller.permission) is deny.

    Suggestion (treat deny as not existing): add a guard in execute before Agent.get:

    if (ctx.agent && PermissionNext.evaluate("task", params.subagent_type, ctx.agent.permission).action === "deny") {
      throw new Error(`Unknown agent type: ${params.subagent_type} is not a valid agent type`)
    }

    This keeps denied targets hidden and prevents self-dispatch loops (e.g., @sre -> task(subagent_type: "sre")).

  3. github-actions commented on Apr 1, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 90 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions