v1.20.02026-08-26
The compile loop, closed. Two new tools on the vs-debug server, both ungated. Full reference: docs/BUILD.md.
Features
- vsbuild - Claude builds with Visual Studio's own build. Not a separate dotnet build in a terminal, but the build the IDE runs: your solution's active configuration, your .NET Framework and C++ projects, your Directory.Build.props chain, your analyzers. It returns structured errors and warnings as {file, line, column, message, project} plus the tail of the build log. The reason this matters more than it sounds: the Error List that getDiagnostics reads is populated by the IDE's build, so until now Claude's view of your compiler errors was whatever your last manual Ctrl+Shift+B left behind - possibly from before the edit it had just made. Pass project to build one project, by name or by the path of any file inside it. The build runs asynchronously so the IDE stays responsive, and a build that outlasts the timeout keeps going: calling vsbuild again attaches to the same build instead of starting a second one.
- vsreadoutput - Claude reads the Output window. Under F5 a lot of what your program says never reaches a terminal: Debug/Trace output, assembly-binding failures, Hot Reload messages, and first-chance exception notices even when the exception is caught and swallowed. All of it goes to the Output window's Debug pane, which no CLI can see. This reads any pane - debug, build, general, or one by name - tailed to a bounded number of lines with an optional contains filter. The swallowed exception is the case worth having it for: no stdout, no failing test, exit code zero, and the Debug pane has been recording it the whole time.
Diagnostics are read from the Error List rather than by parsing the build log, deliberately: MSBuild's error and warning keywords are localized, so a regex over that text would work in an English Visual Studio and nowhere else. For the same reason the three pane aliases are matched by pane id, not display name - the Build pane is 生成 in a Chinese VS. The raw log still ships alongside the structured rows, because MSBuild-level failures (a failed restore, a missing SDK or target) do not always produce Error List rows, and when the build fails with no rows the response says so and points at the log.
New fixture: demo/BuildBreak - two projects, one deliberately broken with a compile error and a warning, one that builds clean and swallows an exception only the Debug pane records.
Fixes
- The single gate stops double-prompting when Claude runs from a parent folder. Open a solution from a subfolder (demo\BuildBreak.slnx) but run claude from above it (demo\), and the session-ownership check called that session foreign: it required the session's folder to be inside the workspace and did not accept the reverse. /permission then answered "ask", which sends the decision back to the CLI's own permission prompt - and because the CLI is connected to the IDE, it renders that prompt as its own diff. The visible result was the extension appearing to break in two ways at once: the panel's auto-accept toggle did nothing (the gate refuses before auto-accept is ever consulted), and accepting the diff left the terminal's Accept/Reject prompt still sitting there, because that prompt was the CLI's, not ours. Ownership is now containment in either direction, and an edit whose target file lives inside the workspace is owned outright regardless of where the session was started. Two genuinely unrelated folders are still refused, which is the multi-instance case the check exists for. The four hook scripts and the MCP shim rank bridges the same way, so discovery and gating agree; the shim also gets the separator-aware match the hooks already had, so C:\work\app no longer matches C:\work\app-service.
- Diff staging files stop polluting the diagnostics. Every diff stages a claudediff.tmp / claudeperm.tmp, and opening one in the diff viewer gets it analyzed as a miscellaneous file. Those Error List rows outlived the file: they survived its deletion and later builds, accumulating a fresh set per edit, so error and warning counts drifted upward and pointed at temp paths that no longer existed. Both getDiagnostics and vs_build now filter them at the read, which also clears rows already accumulated in a running session.
- Closing a diff's tab no longer hangs the edit. Accept, Reject, and dismissing the info bar all completed the edit; closing the diff window itself - the tab's X, Ctrl+F4, Close All Documents - did not. Closing the frame does not raise the info bar's own closed event, so the reply the CLI was waiting on never came: the edit sat unanswered indefinitely with its staged temp file still on disk, which reads as Claude having frozen mid-tool. Closing the window now counts as a reject, the same as dismissing the bar.
- vsbuild always returns the whole build log.** The log had been read as a delta from a bookmark taken before the build, which cannot be made to work: Visual Studio clears the pane at the start of every build, and no bookmark distinguishes that reliably from the log simply growing. The symptom was a log that started halfway through, or came back missing entirely when two consecutive builds were the same length. The pane is now cleared before the build starts and read whole, which is deterministic. The structured errors and warnings were never affected (they come from the Error List), but the raw log is the only place MSBuild-level failures appear, which is the whole reason it is there.
- The panel's run-wild checkbox stops lying about the CLI's mode. The checkbox locks itself checked and disabled while the CLI session is in a mode that pre-approves edits, since unchecking it could not re-gate what the CLI had already approved. But the mode was only ever sampled from the edit-gate hook, which fires on edits alone - so a session that switched into auto mode, made one edit, then switched back out left the checkbox stuck checked and unclickable indefinitely, with the tooltip pointing at a terminal toggle that appeared to do nothing. The mode is now re-sampled from every prompt and every turn end, so it catches up within one exchange.
- A friendly message in Open Folder mode. vs_build with a folder open but no solution returned EnvDTE's raw Value cannot be null. Parameter name: pSlnCfg. It now detects the missing build configuration up front and says what to do about it.