Skip to content

[BUG] Windows: Read tool PDF fails with 'pdftoppm is not installed' only in VS Code extension sessions - same binary/env/cwd succeeds headless (-p); negative resolver result cached for process lifetime #73696

Description

@carhero

Summary

On Windows, the built-in Read tool fails to render PDFs with:

pdftoppm is not installed. Install poppler-utils (e.g. `brew install poppler` or `apt-get install poppler-utils`) to enable PDF page rendering.

— but only in interactive sessions spawned by the VS Code extension. The exact same native binary, run headless (claude -p) with the same environment, same cwd, and even the same stdio shape (piped stream-json in/out), reads the same PDF successfully. poppler is correctly installed and on PATH, and the session's own Bash/PowerShell tools run pdftoppm fine.

This looks like the Windows executable-discovery path (where.exe-based resolver) failing inside IDE-spawned interactive processes, compounded by the resolver caching the negative result for the lifetime of the process with no retry, so the session can never recover.

Environment

  • Windows 10 Pro 10.0.19045
  • VS Code extension anthropic.claude-code-2.1.199-win32-x64 (native binary resources\native-binary\claude.exe, reports 2.1.199 (Claude Code))
  • Also tested: npm global @anthropic-ai/claude-code 2.1.197 (headless — works)
  • poppler 26.02.0 (oschwartz10612/poppler-windows) at %LOCALAPPDATA%\poppler\poppler-26.02.0\Library\bin, registered in HKCU user PATH
  • Shell tools in the failing session resolve pdftoppm fine (Git Bash and PowerShell)
  • Corporate antivirus present (considered as a factor; see "Ruled out" — headless descendants of the very same process tree succeed)

What fails vs. what works (all on the same machine, same day, after reboot)

Scenario Result
Read tool on a PDF, VS Code extension interactive session (fresh process, spawned by extension host) pdftoppm is not installed — persistent, from the very first attempt
Same, in a second independent IDE session (also fresh post-reboot process) ❌ same error
pdftoppm -v via the session's Bash/PowerShell tools ✅ exit 0, version on stderr
Same extension binary run -p headless from a shell: claude.exe -p "Read page 1 of X.pdf" --allowedTools Read ✅ Read succeeds, page rendered
Same binary, -p --input-format stream-json --output-format stream-json with piped stdio and IDE-matching flags (--max-thinking-tokens 31999 --setting-sources=user,project,local --permission-mode acceptEdits) ✅ Read succeeds
npm-installed 2.1.197 headless ✅ Read succeeds

Evidence that the failing process's runtime context is healthy

Verified against the live failing process (extension-spawned claude.exe, PID inspected from within the session):

  1. Process environment PATH contains the poppler directory (read directly from the process environment block — not from a child shell, so no profile/snapshot contamination).
  2. Process CWD is the workspace root on a different drive than poppler, so the resolver's "unsafe location (current directory)" filter cannot apply (candidate is not under cwd).
  3. Manual replay of every step of the discovery pipeline from the same machine succeeds: C:\Windows\System32\where.exe pdftoppm → finds ...\poppler-26.02.0\Library\bin\pdftoppm.exe, exit 0; pdftoppm -v → exit 0, 147 bytes on stderr.
  4. No where.exe (or pdftoppm.exe) process is created when the Read tool fails (observed with a WMI process-creation watcher polling at 30 ms while triggering Read). Combined with (5), this indicates the resolver returns a cached negative without re-probing.
  5. From reading the bundled resolver logic in the 2.1.199 binary: on Windows, bare command names are resolved by spawning where.exe <cmd> with env: process.env, filtering results (lstat-exists, not-under-cwd, extension allowlist .com/.exe/.bat/.cmd), and storing a null result in an in-memory Map. The availability probe path calls the resolver with the "retry negatives" flag set to false, so once a negative is cached, every subsequent PDF read in that session fails instantly and permanently. A failed resolution is synthesized as exit code 127 with the message Command 'pdftoppm' not found or is in an unsafe location (current directory), which the PDF path maps to the generic "pdftoppm is not installed" error — masking the real cause.

Ruled out

  • Stale PATH in the process (verified in the process env block, post-reboot).
  • CWD-based "unsafe location" filter (cwd is on E:, poppler on C:).
  • Shell-profile-only PATH (poppler is in HKCU registry PATH; no PowerShell profile exists; bash profiles contain no poppler entries).
  • settings.json env blocks / claudeCode.environmentVariables overrides (none configured anywhere).
  • Binary/version difference (the literally same .exe file works headless).
  • stdio shape (piped stream-json headless run also works).
  • AV blocking the image chain (headless successes were grandchildren of the failing process itself: failing claude.exe → PowerShell tool → claude.exe headless → where.exe/pdftoppm all fine).

What I could not determine from outside is why the very first where.exe probe fails (or gets filtered) specifically in extension-spawned interactive processes — that needs visibility inside the process (logging around the resolver would answer it immediately).

Suggested fixes

  1. Don't cache negative resolutions for the process lifetime (or expire them / retry on each new tool invocation). A user who installs poppler mid-session, or hits any transient failure, is currently stuck until the session process restarts — and in the IDE that's opaque.
  2. Surface the underlying resolution error instead of collapsing everything into "pdftoppm is not installed" (distinguish: not on PATH / filtered as unsafe-location / where.exe itself failed / spawn error).
  3. Consider an fs-based PATH+PATHEXT scan fallback when where.exe yields nothing, and/or log the PATH actually used for the probe at debug level.

Workaround for affected users

Inside the affected IDE session, the Bash tool resolves PATH normally, so:

pdftoppm -png -r 150 -f 1 -l 1 file.pdf /tmp/out   # then Read the PNG(s)
pdftotext -layout file.pdf out.txt                  # when only text is needed

Related


This investigation was performed by Claude Code itself (Fable 5) from inside the affected session — including reading the live process environment/CWD, replaying the discovery pipeline, bisecting IDE vs. headless spawn conditions, and inspecting the bundled resolver logic.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:toolsbugSomething isn't workinghas reproHas detailed reproduction stepsplatform:vscodeIssue specifically occurs in VS Codeplatform:windowsIssue specifically occurs on Windows

    Type

    No type

    Fields

    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