Skip to content

Conversation

@randomm
Copy link

@randomm randomm commented Jan 7, 2026

Summary

Fixes subagent permission bug where tools field was spread into resolved agent config, causing permission blocks to be ignored for tool availability.

Root Cause

In packages/opencode/src/config/config.ts around line 529, the Agent schema transform spreads the entire agent object:

return { ...agent, options, permission, steps }

This preserves the tools field alongside the new permission field, causing tools to override permission-based tool availability.

The Fix

Destructure to exclude tools before spreading:

const { tools, maxSteps, ...rest } = agent
return { ...rest, options, permission, steps }

Testing

  • Built OpenCode from source with this fix
  • Verified with opencode debug agent <name>:
    • System v1.0.220: tools field PRESENT (broken)
    • Fixed build: tools field ABSENT (working)
  • Subagent permission blocks now properly control tool availability

Fixes #6527
Related to #6873

Fixes subagent permission bug where tools field was spread into
resolved agent config, causing permission blocks to be ignored.

Root cause: config.ts line 529 spread ...agent which included tools.
Fix: destructure to exclude tools before spreading.

Relates to: anomalyco#6527
@github-actions
Copy link
Contributor

github-actions bot commented Jan 7, 2026

The following comment was made by an LLM, it may be inaccurate:

No duplicate PRs found

@randomm
Copy link
Author

randomm commented Jan 7, 2026

⚠️ Update: This fix is necessary but not sufficient

After further investigation, we found a SECOND issue that also needs to be addressed:

Issue 1 (this PR): config.ts line 529 - tools field spread into agent config
Issue 2 (not yet fixed): registry.ts - ToolRegistry.tools() doesn't filter by agent permissions

The tools() function in packages/opencode/src/tool/registry.ts accepts an agent parameter but doesn't use it to filter tools based on agent.permission. It returns ALL tools regardless of what the agent should have access to.

We're working on a second fix for this and will either:

  1. Add it to this PR, or
  2. Submit a separate PR

Will update shortly.

Adds permission-based filtering to ToolRegistry.tools() so subagents
only see tools they're allowed to use based on their permission config.

- Check agent.permission if provided
- Use PermissionNext.evaluate() to check each tool
- Filter out tools with 'deny' action

This complements the config.ts fix that excludes the tools field
from agent config spread.

Fixes anomalyco#6527
@randomm
Copy link
Author

randomm commented Jan 7, 2026

Second fix added

Just pushed the complete fix. This PR now includes:

  1. config.ts (line 529): Exclude tools field from agent config spread
  2. registry.ts (line 129-135): Filter tools by agent permissions in ToolRegistry.tools()

The tools() function now:

  • Checks if agent?.permission exists
  • Uses PermissionNext.evaluate() to check each tool
  • Filters out tools with 'deny' action

Together, these fixes ensure subagents only see tools they're permitted to use.

Janni Turunen added 2 commits January 7, 2026 21:47
The previous fix incorrectly filtered out tools like bash when they had
permission rules with a default deny (e.g., { 'git*': 'allow', '*': 'deny' }).

New logic:
- If permission is an object with rules, allow the tool (rules enforced at execution)
- If permission is explicitly 'deny' string, filter out the tool
- If permission is 'allow', 'ask', or undefined, allow the tool

This ensures git-agent gets bash tool access while still respecting
command-level restrictions at execution time.
Previous fix used PermissionNext.evaluate(toolId, '*', permission) which
incorrectly matched catch-all deny rules.

Correct logic:
- If permission[toolId] is an object (has rules), allow the tool
- If permission[toolId] is 'deny' string, filter out the tool
- Command-level restrictions enforced at execution time
@randomm
Copy link
Author

randomm commented Jan 7, 2026

Final Fix Applied (v1.0.220-fix4)

The complete fix is now in this PR. Key insight:

The Problem with Previous Approach

Using PermissionNext.evaluate(toolId, '*', permission) doesn't work for tool availability because:

  • For bash: { 'git*': 'allow', '*': 'deny' }
  • The evaluate function with pattern '*' matches the catch-all deny rule
  • Result: bash tool filtered out entirely instead of being available with command restrictions

Correct Approach

Check the permission TYPE directly:

if (agent?.permission) {
  const toolPermission = (agent.permission as unknown as Record<string, unknown>)[t.id]
  
  // Object with rules → allow tool (command restrictions at execution time)
  if (toolPermission !== null && typeof toolPermission === 'object') {
    return true
  }
  
  // Explicit 'deny' string → filter out tool
  if (toolPermission === 'deny') {
    return false
  }
}

Why This Works

  • Object detection: If permission has command-level rules, the tool is configured and should be available
  • String handling: Only explicit 'deny' filters out the tool
  • Separation of concerns: Tool availability ≠ command permissions. Command restrictions enforced at execution time by existing permission system.

Built and tested - git-agent now properly receives bash tool with command restrictions.

- Check permission array for tool-specific rules
- Only filter out tools where ALL rules are deny
- Support catch-all deny for tools without explicit rules
- Add comprehensive test suite (12 tests)

Fixes anomalyco#6527
@randomm
Copy link
Author

randomm commented Jan 8, 2026

⚠️ Update: Fix is Incomplete

After extensive testing, we've found that our fix does NOT fully resolve the issue.

What We Tried

  1. config.ts (line 529): Exclude tools field from agent config spread
  2. registry.ts: Filter tools by checking permission array for tool-specific rules

What Happens

  • The tools field IS removed from debug output ✅
  • Permission rules ARE present in agent config ✅
  • But subagents still don't receive the correct tools ❌

Root Cause Analysis

The issue appears deeper than just config spreading. Subagents invoked via Task tool still report missing tools (e.g., git-agent configured with bash permissions has no bash access).

Key Observation

  • v1.0.200: Subagents work correctly (git-agent has bash)
  • v1.0.220+: Subagents broken (git-agent has no bash)
  • Our fix built from source: Still broken

The tool availability for subagents appears to be determined elsewhere, possibly in task.ts, prompt.ts, or llm.ts.

We're leaving this PR open for reference but do not recommend merging until the actual root cause is found.

cc @thdxr

@randomm
Copy link
Author

randomm commented Jan 8, 2026

Closing this PR as our fix doesn't address the actual root cause.

What we found:
The real issue is in the interaction between task.ts and prompt.ts:

  • task.ts calls SessionPrompt.prompt() with a tools parameter
  • prompt.ts converts this to permissions and replaces session.permission entirely (lines 159-172)
  • This wipes out any subagent permission config

Our changes to Session.create() and ToolRegistry.tools() don't help because the permissions get overwritten immediately after.

Our plan:

@randomm randomm closed this Jan 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security Issue/Bug] Plan mode restrictions bypassed when spawning sub-agents

1 participant