Skip to content

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

@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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions