Skip to content

Non-interactive (-p) sessions: tool-call approval silently and permanently revoked mid-session, causing unrecoverable 'Permission denied and could not request permission from user' #4433

Description

@nsd0okernicke

Summary

In non-interactive mode (-p/--prompt), long sessions (roughly 4-8 minutes, many tool
calls) start denying every write-capable tool call and some read-only diagnostic commands with:

Permission denied and could not request permission from user

...partway through the session, with no recovery for the rest of that session. This happens
even with --allow-all (--allow-all-tools --allow-all-paths --allow-all-urls) passed
explicitly, and even after also adding explicit --allow-tool=read --allow-tool=write --allow-tool=shell rules alongside it. Short sessions with byte-for-byte identical flags,
working directory, and --agent never reproduce it.

Environment

  • GitHub Copilot CLI version: 1.0.79 (also reproduced on 1.0.78)
  • OS: Windows 11 Pro (10.0.26200)
  • Node.js: v24.18.1
  • Invocation: -p/--prompt (non-interactive mode), --output-format json, piped
    stdout/stderr, stdin redirected from a null device (no TTY)
  • Auth: personal GitHub account, not part of any org-managed/enterprise Copilot policy
    (confirmed via logs, see below)

Exact command

copilot -p "<task prompt>" --agent <custom-agent-name> --allow-all \
  --allow-tool=read --allow-tool=write --allow-tool=shell \
  --output-format json --disable-mcp-server kiln-db --disable-builtin-mcps

<custom-agent-name> resolves a project-level .github/agents/<name>.agent.md file with:

---
name: <custom-agent-name>
description: ...
tools:
  - read
  - write
  - shell
---

What happens

Deep into the session (never at the start — always after substantial legitimate activity:
reading files, running git status, listing directories, etc.), every subsequent
write-capable tool call starts failing:

  • create/edit built-in tools
  • PowerShell write commands: New-Item, mkdir, Set-Content, Add-Content, cmd /c mkdir,
    even Python's os.makedirs/open() invoked via a powershell tool call
  • git commit, git hash-object -w (write-capable git subcommands)
  • All MCP tool calls (including ones explicitly targeted by --disable-mcp-server — see below)

...while these keep working throughout the same session, after the denials begin:

  • Read-only content commands: Get-ChildItem, Get-Content, view, grep, glob
  • Read-only git commands: git status, git branch -a, git log (sometimes — see
    inconsistency note below)
  • Plain stdout commands with no filesystem/state mutation: Write-Host, echo

In the most detailed capture, the model also discovered that a write into Copilot's own
internal session workspace succeeded even after writes to the actual project working
directory were being denied:

● Create test.txt +1
  └ ~\.copilot\session-state\5ee8426d-2c83-43e5-8ace-09549382de6f\files\test.txt

...and that system-introspection commands specifically also got denied, even though they're
read-only: whoami, icacls <path>, Get-Acl <path>.

The exact same directory, exact same flags, exact same --agent, run as a short session
(a single trivial write request) succeeds every time — I confirmed this repeatedly, including
immediately after a failed long run, in the identical working directory.

Inconsistency note

The denial isn't a clean "reads work, writes don't" split. In different failed runs:

  • Run A: git status/git branch -a kept succeeding throughout, only writes and MCP calls
    were denied.
  • Run B: git status/git branch -a/git log were also denied on some invocations
    (git status failed once, then succeeded on retry, then failed again later in the same
    session) — not fully deterministic even for the same command type.

What I've ruled out

  • Not workspace trust — the working directory is confirmed present in
    ~/.copilot/config.json's trustedFolders.
  • Not the permission flag itself--allow-all is already the maximum grant the CLI
    documents; adding explicit --allow-tool rules alongside it made no difference.
  • Not session/tool-call volume alone — a synthetic session issuing 30-40 sequential trivial
    shell calls (no real project content) never reproduces it, regardless of call count.
  • Not the --disable-mcp-server/--disable-builtin-mcps combination alone — in isolated
    short tests with those same flags, a disabled MCP server (kiln-db, backed by a project-local
    .mcp.json) is reported as absent from the toolset (never attempted). In the real failing
    sessions, the same disabled server is visible and attempted, then denied — i.e. the disable
    flags behave differently between short and long/real sessions.
  • Not the working directory itself — a fresh short session in the exact same directory
    writes successfully immediately before and after a failed long run in that directory.
  • Not an active enterprise/managed policy for this account — the CLI's own log at session
    start confirms:
    [managedSettings] server policy: none for this account (404/empty) from https://github.com
    [managedSettings] effective policy resolved: source=none, bypassDisabled=false, serverFetchFailed=false
    [managedSettings] applied: no bypass restriction in force (managed policy absent)
    
  • Not (as far as I can tell) local/org sandboxing~/.copilot/settings.json does not
    exist at all on this machine, and sandboxing is documented as off by default without one.

What I haven't been able to confirm

  • --log-dir <dir> --log-level all (Copilot's own verbose internal trace) works perfectly for
    short sessions (150KB+ of detail, including AUTO_APPROVAL/trust-decision lines) with
    byte-for-byte identical flags/working directory/--agent to the failing runs — but comes back
    completely empty for every long session that actually hits this bug, as if whatever
    internal state causes the tool-call denials also prevents the custom log target from being
    written/flushed. The default log location (~/.copilot/logs/) still receives Copilot's normal
    (much sparser, INFO-level-only) log for these sessions, but it contains no detail about
    individual permission decisions — only process lifecycle/MCP-connection lines.
  • I decompiled the shipped Windows bundle (@github/copilot-win32-x64/app.js, 1.0.79) looking
    for the mechanism and found a real, related-looking code path —
    beginManagedSettingsResolution() / bypassPermissionsDisabledByPolicy /
    disableBypassPermissionsMode / a fail-closed branch that zeroes out
    approveAllToolPermissionRequests (but not approveAllReadPermissionRequests) if a
    managed-settings resolution can't complete — but as noted above, this account has no managed
    policy, so I can't confirm this exact path is what's firing here. It may be a related but
    distinct mechanism, or the same code triggering for a different reason (e.g. a transient
    self-fetch failure independent of having a real org policy).

Why this matters for non-interactive/scripted use specifically

The error text itself — "...and could not request permission from user" — reads as
Copilot's own approval broker attempting to raise a prompt and failing because there's no
interactive terminal to answer it. In an interactive session the same internal event would just
surface as a visible, answerable prompt. For scripted/CI/agent-orchestration use of -p mode,
this is a hard, silent, unrecoverable failure partway through otherwise-successful long-running
work, with no documented flag that prevents or recovers from it.

Ask

  • Confirmation of whether this is the managed-settings fail-closed path, sandboxing, or a third
    mechanism.
  • Whether there's a supported flag/setting to keep tool-call approval stable for the full
    duration of a long non-interactive session, or a way to force a fresh, clean re-resolution
    instead of failing closed.
  • Any plan to make --log-dir/--log-level all reliably capture what happens once a session
    enters this state, since right now the one diagnostic tool built for exactly this situation
    goes silent precisely when it's needed most.

Happy to provide the full JSONL session transcripts (via --resume=<session-id>) or run
additional diagnostics against a debug build if useful.

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