Skip to content

Copilot SDK tool permissions denied by default - agents cannot use filesystem tools #14

@e-s-gh

Description

@e-s-gh

Problem

Starting with Copilot SDK v0.1.28 (copilot-sdk PR #509), all tool operations (file reads/writes, shell commands, URL fetches, MCP calls) are denied by default unless the application provides an on_permission_request handler.

Previously, permissions were only requested when a handler was explicitly provided. Now requestPermission: true is always sent in session.create and session.resume RPC calls. Without a handler, the CLI returns:

Permission denied and could not request permission from user

This affects all Conductor workflows — every agent's tool calls (view, powershell, grep, glob, edit, etc.) silently fail, causing agents to either:

  • Skip tool usage entirely and produce results with incomplete context
  • Attempt multiple tool calls, get denied on all, then report "access restrictions"

Reproduction

# Any workflow where agents need filesystem tools
conductor run workflow.yaml --input source_dir="/path/to/code"
# Agent output will report: "Permission denied and could not request permission from user"

A minimal debug workflow is available at examples/docs-eval/debug-tools.yaml that isolates the issue by testing view, powershell, and grep tool access against a target directory.

Root Cause

In src/conductor/providers/copilot.py, the _execute_sdk_call method builds a session_config dict passed to client.create_session(). The config has no on_permission_request handler, triggering the SDK's deny-by-default behavior:

# copilot SDK internal (session.py)
async def _handle_permission_request(self, request):
    if not handler:
        return {"kind": "denied-no-approval-rule-and-could-not-request-from-user"}

Proposed Fix

Add an on_permission_request handler to the session config. The SDK defines 5 permission request kinds and 4 result kinds:

Permission request kinds: shell, write, read, url, mcp

Permission result kinds: approved, denied-by-rules, denied-interactively-by-user, denied-no-approval-rule-and-could-not-request-from-user

Minimal fix (auto-approve all)

def _auto_approve_tool(request, context):
    return {"kind": "approved"}

session_config["on_permission_request"] = _auto_approve_tool

This matches the --allow-all behavior the SDK FAQ states should be the default.

Recommended: Expose as workflow configuration

Add an optional tool_permissions field to the workflow runtime config, allowing per-workflow control of tool permissions:

workflow:
  runtime:
    provider: copilot
    tool_permissions:
      default: allow              # "allow" (default) | "deny"
      rules:
        - kind: shell
          decision: deny          # deny shell for sandboxed workflows
        - kind: write
          decision: deny          # read-only evaluation workflows
        # read, url, mcp inherit from default: allow

Implementation sketch:

# schema.py
class ToolPermissionRule(BaseModel):
    kind: Literal["shell", "write", "read", "url", "mcp"]
    decision: Literal["allow", "deny"] = "allow"

class ToolPermissionsConfig(BaseModel):
    default: Literal["allow", "deny"] = "allow"
    rules: list[ToolPermissionRule] = Field(default_factory=list)

class RuntimeConfig(BaseModel):
    # ... existing fields ...
    tool_permissions: ToolPermissionsConfig = Field(default_factory=ToolPermissionsConfig)
# copilot.py - in _execute_sdk_call()
def _make_permission_handler(config: ToolPermissionsConfig):
    rules = {r.kind: r.decision for r in config.rules}
    def handler(request, context):
        kind = request.get("kind", "")
        decision = rules.get(kind, config.default)
        if decision == "allow":
            return {"kind": "approved"}
        return {"kind": "denied-by-rules"}
    return handler

When no tool_permissions is specified, default to allow — maintaining backward compatibility and matching the SDK FAQ's stated behavior.

Use cases for granular control

  • Read-only evaluation workflows: allow read but deny write and shell
  • Sandboxed/untrusted workflows: deny all except read
  • Audit logging: log all permission decisions without blocking

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:providerSDK providers (Copilot, Claude)bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions