Include Claude debug artifacts in Export Agent Host Debug Logs#327374
Include Claude debug artifacts in Export Agent Host Debug Logs#327374TylerLeonhardt wants to merge 7 commits into
Conversation
The export command previously surfaced only Copilot's logs. Advertise the Claude SDK `--debug` log and the session transcript JSONL through the AHP session `_meta` bag (well-known `debugArtifacts` key, mirroring `agentHost.git`), so the client reads concrete host-side paths instead of hard-coding a provider's on-disk layout — no protocol change. - Shared log-naming helpers (`agentHostLogNaming`) reused by the AHP JSONL logger and Claude debug-file naming (sanitize + file-safe timestamp). - `ClaudeAgentSession` points the SDK at a per-run debug file and publishes the discovered artifacts (log + transcript) into `_meta`. Discovery/publishing is extracted into a testable `ClaudeDebugArtifacts` class backed by an observable that is a pure projection of disk truth. - Export resolves the `AgentHostSessionAdapter` that carries `_meta` and streams each artifact via the resource proxy (`file://` local / `vscode-agent-host://` remote), with provider-agnostic remote authority resolution. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds Claude SDK debug logs and session transcripts to Agent Host debug exports via session metadata.
Changes:
- Adds Claude artifact discovery, naming, and metadata publication.
- Exports advertised artifacts through local or remote resource proxies.
- Adds unit coverage for naming, discovery, metadata, and SDK options.
Show a summary per file
| File | Description |
|---|---|
agentHostLogSources.ts |
Generalizes remote connection lookup and log discovery. |
exportAgentHostDebugLogsAction.ts |
Collects and exports advertised artifacts. |
exportDebugLogsAction.ts |
Supplies Agents-window session artifacts. |
baseAgentHostSessionsProvider.ts |
Exposes adapter debug artifacts. |
claudeSdkOptions.test.ts |
Tests SDK debug-file projection. |
claudeDebugArtifacts.test.ts |
Tests artifact discovery and preparation. |
sessionDebugArtifactsMeta.test.ts |
Tests metadata serialization and validation. |
agentHostLogNaming.test.ts |
Tests naming and artifact projection. |
claudeSdkOptions.ts |
Configures the Claude SDK debug file. |
claudeDebugArtifacts.ts |
Discovers Claude logs and transcripts. |
claudeAgentSession.ts |
Publishes discovered artifacts into session metadata. |
sessionState.ts |
Defines the debug-artifact metadata contract. |
ahpJsonlLogger.ts |
Reuses shared naming helpers. |
agentHostLogNaming.ts |
Adds shared naming and projection utilities. |
Review details
Comments suppressed due to low confidence (1)
src/vs/workbench/contrib/chat/browser/actions/exportAgentHostDebugLogsAction.ts:386
- This still uses the Copilot-only parser, so
remote-<authority>-claude(and every other non-Copilot provider) returnsundefined, contrary to this function's updated contract. As a result, the workbench action treats a remote Claude chat as having no active Agent Host session and omits all session-specific logs. Use the provider-agnostic remote-session-type predicate here.
if (parseRemoteAuthorityFromScheme(resource.scheme)) {
return { resource, title, isLocal: false };
- Files reviewed: 14/14 changed files
- Comments generated: 4
- Review effort level: Medium
| if (activeSession?.isLocal) { | ||
| const debugArtifacts = await resolveLocalDebugArtifacts(agentHostService, activeSession.resource); |
| // The original `accessor` is invalid after the await above, so re-enter with | ||
| // a fresh one for the (accessor-threading) export helper. | ||
| await instantiationService.invokeFunction(accessor => exportAgentHostDebugLogs(accessor, activeSession)); |
| const token = claudeDebugLogSessionToken(this._sessionId); | ||
| try { | ||
| const stat = await this._fileService.resolve(joinPath(this._logsHome, CLAUDE_LOG_DIR)); | ||
| return (stat.children ?? []).filter(c => !c.isDirectory && c.name.endsWith('.log') && c.name.includes(token)).map(c => c.resource.fsPath); |
…ts, drop invokeFunction - claudeDebugArtifacts: match the exact `-<token>.log` suffix instead of `includes(token)`, so a sibling session whose id merely contains this token (e.g. `sess-abc` vs `sess-abc-extra`) can't leak its debug log into the export. - claudeAgentSession: let an empty projection clear the `_meta` debugArtifacts slot (withSessionDebugArtifacts already removes only this slot), keeping the advertised state a true projection of disk; skip only the initial empty write. - export action: resolve debug artifacts inside collectAgentHostDebugLogs for both local and remote sessions (remote via the matching IAgentConnection), and drop the invokeFunction re-entry so run() uses the accessor synchronously. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Addressed all four review comments in 668628a:
The Linux / Electron check failure was an unrelated flake — |
|
Base:
|
Both IAgentHostService and a remote IAgentConnection structurally expose `listSessions()`, so resolveSessionDebugArtifacts can take the owning host directly rather than a `() => host.listSessions()` thunk. Pick the lister once (local service or remote connection) and pass it, collapsing the two branches. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
resolveSessionDebugArtifacts had one call site and ~3 functional lines; fold it into collectAgentHostDebugLogs. `sessions` now infers its type from the picked lister's listSessions(), so the IAgentSessionMetadata import drops too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three cleanups from PR review: - Compute the transcript path from the cwd instead of scanning every ~/.claude/projects directory. The CLI slug is `cwd.replace(/[^a-zA-Z0-9]/g, '-')` (verified against the on-disk project dirs), so resolving the transcript is a single `stat`, not O(projects). claudeProjectSlug / buildClaudeTranscriptPath are pure and unit-tested. - Drop the observable + autorun. ClaudeDebugArtifacts.refresh(cwd) now returns the projected artifacts and the session's _publishDebugArtifacts writes them to `_meta` directly, at the three change points (materialize / rematerialize / first turn). With one producer and one consumer the observable was pure indirection, and its autorun fired a wasted no-op run with [] on registration. - ClaudeDebugArtifacts is now stateless; tests await refresh() rather than reading an observable, plus a new test proves the transcript is addressed by cwd slug (a same-id file under a different project dir is ignored). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Applies two learnings recovered from the old copilot-chat computeFolderSlug (which was removed when Claude session handling moved to the SDK's listSessions): - Windows: VS Code lowercases the URI drive letter, but the CLI slugs its native uppercase process.cwd(), so claudeProjectSlug now re-uppercases a leading `<drive>:` (`c:\x` -> `C--x`). Without this the transcript was silently missed on Windows. - The slug is only a best-effort match (a symlinked cwd, an unusual path char the encoder gets wrong, or a CLI version change all make it miss), so _readTranscriptPath now computes the path first (one stat) and, only on a miss, falls back to a bounded scan of ~/.claude/projects/* for <sessionId>.jsonl (newest). O(1) in the common case; correct in the edge cases. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Review details
Comments suppressed due to low confidence (1)
src/vs/workbench/contrib/chat/browser/actions/exportAgentHostDebugLogsAction.ts:371
- Remote Claude sessions are still excluded here:
parseRemoteAuthorityFromSchemeonly acceptsremote-<authority>-copilotcli(seecopilotCliEventsUri.ts:85-92). In the workbench action this makesactiveSessionundefined, socollectAgentHostDebugLogsnever queries the remote Claude host's_meta. Use the provider-neutralisRemoteAgentHostSessionTypecheck; authority resolution is already handled later bygetRemoteConnectionForSession.
* `undefined` if the scheme does not belong to an agent-host session. Covers any
* local agent host (`agent-host-<provider>`, e.g. Copilot CLI or Claude) and
* remote agent hosts; the EH CLI extension's own `copilotcli:` sessions are
- Files reviewed: 14/14 changed files
- Comments generated: 2
- Review effort level: Medium
| const resource = activeSession?.isLocal | ||
| ? URI.file(artifact.path) | ||
| : artifactAuthority ? toAgentHostUri(URI.from({ scheme: Schemas.file, path: artifact.path }), artifactAuthority) : undefined; |
| // Capture the size so remote artifacts use the bounded read path (a large | ||
| // `--debug` log shouldn't be slurped whole over the proxy), and a missing | ||
| // file (e.g. one whose SDK subprocess hasn't written it yet) is skipped. | ||
| const { size } = await fileService.resolve(resource, { resolveMetadata: true }); | ||
| files.push(await createDebugLogFile(`${sanitizeFilePart(artifact.label)}${extname(resource)}`, resource, fileService, size)); |
Two remote-path review comments:
- A Windows agent host advertised raw fsPaths like `C:\Users\...`, which the
client cannot wrap (`URI.from`/`URI.file` mangle a foreign drive/UNC path). The
host now encodes each artifact as a `file://` URI string (it owns its own path
semantics); the client just parses it and, for remote, wraps it for the
connection authority. Renamed the IDebugArtifact field `path` -> `uri`.
- The AHP filesystem provider has no ranged read, so `readFileStream({length})`
still fetched the whole remote file into the renderer (then re-buffered it).
Cap by stat size in createDebugLogFile: above 50 MiB, export a short note
instead of slurping a runaway `--debug` log.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Addressed the two new review comments in e7a7618:
(The other four inline comments are the original 2026-07-24 review, already fixed in 668628a.) Verified: typecheck + eslint + layers clean, 12673 node tests passing. Both fixes are on the remote path, which I can't exercise in the dev sandbox — CI covers it. |
What & why
The Export Agent Host Debug Logs command previously surfaced only Copilot's logs. This includes the Claude backend's debug artifacts in the export:
--debuglog (enabled per-run viaOptions.debugFile), andevents.jsonl).How it works
Rather than hard-coding a provider's on-disk layout in the client, the host advertises its artifacts through the AHP session
_metabag under a well-knowndebugArtifactskey — mirroring the existingagentHost.gitprecedent. No protocol change.agentHostLogNaming(new, common) — shared, pure log-naming helpers (sanitize + file-safe timestamp) reused by both the AHP JSONL logger and Claude debug-file naming, plus the purebuildClaudeDebugArtifactsprojection.ClaudeDebugArtifacts(new, node) — owns discovery + publishing: points the SDK at a per-run debug file, globs the log(s) (current + prior runs) and the transcript, and exposes them as an observable that is a pure projection of disk truth.ClaudeAgentSessionis a thin consumer that bridges the observable into_meta.AgentHostSessionAdaptercarrying_metaand streams each artifact via the resource proxy (file://local /vscode-agent-host://remote), with provider-agnostic remote authority resolution.Testing
npm run typecheck-client— cleannpm run valid-layers-check— cleanFileService+InMemoryFileSystemProvider):claudeDebugArtifacts.test.ts— glob discovery / filtering / numbering / transcript /prepareDebugFileagentHostLogNaming.test.ts,sessionDebugArtifactsMeta.test.ts,claudeSdkOptions.test.tsClaude-debug-log.log+Claude-transcript.jsonlwith a matching session id.Draft while the agent-host e2e replay suite runs in CI (the local Electron integration harness can't init in the dev sandbox).
🤖 Generated with Claude Code