Skip to content

Include Claude debug artifacts in Export Agent Host Debug Logs#327374

Draft
TylerLeonhardt wants to merge 7 commits into
mainfrom
tyleonha/claude-export-debug-artifacts
Draft

Include Claude debug artifacts in Export Agent Host Debug Logs#327374
TylerLeonhardt wants to merge 7 commits into
mainfrom
tyleonha/claude-export-debug-artifacts

Conversation

@TylerLeonhardt

Copy link
Copy Markdown
Member

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:

  • the Claude SDK --debug log (enabled per-run via Options.debugFile), and
  • the Claude session transcript JSONL (the analogue of Copilot's events.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 _meta bag under a well-known debugArtifacts key — mirroring the existing agentHost.git precedent. 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 pure buildClaudeDebugArtifacts projection.
  • 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. ClaudeAgentSession is a thin consumer that bridges the observable into _meta.
  • Export (client) — resolves the AgentHostSessionAdapter carrying _meta and streams each artifact via the resource proxy (file:// local / vscode-agent-host:// remote), with provider-agnostic remote authority resolution.

Testing

  • npm run typecheck-client — clean
  • npm run valid-layers-check — clean
  • Node unit suite green, including new deterministic tests (real FileService + InMemoryFileSystemProvider):
    • claudeDebugArtifacts.test.ts — glob discovery / filtering / numbering / transcript / prepareDebugFile
    • agentHostLogNaming.test.ts, sessionDebugArtifactsMeta.test.ts, claudeSdkOptions.test.ts
  • Manually verified end-to-end previously: the exported zip contains Claude-debug-log.log + Claude-transcript.jsonl with 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

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>
Copilot AI review requested due to automatic review settings July 24, 2026 20:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) returns undefined, 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

Comment on lines +345 to +346
if (activeSession?.isLocal) {
const debugArtifacts = await resolveLocalDebugArtifacts(agentHostService, activeSession.resource);
Comment on lines +351 to +353
// 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);
Comment thread src/vs/platform/agentHost/node/claude/claudeAgentSession.ts Outdated
…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>
@TylerLeonhardt

Copy link
Copy Markdown
Member Author

Addressed all four review comments in 668628a:

  1. Remote artifacts not attached (workbench action, lines 346/385) — moved the debug-artifact resolution into collectAgentHostDebugLogs, which already holds both the local and remote agent-host services. It now resolves a remote session's artifacts via the matching IAgentConnection.listSessions(), exactly as it does locally.
  2. invokeFunction DI re-entry (line 353) — with resolution moved into collectAgentHostDebugLogs, run() no longer awaits before the export call, so it uses the accessor synchronously and the invokeFunction re-entry is gone.
  3. includes(token) cross-session log match (claudeDebugArtifacts.ts) — now matches the exact claude-<timestamp>-<token>.log shape (startsWith('claude-') && endsWith('-<token>.log')), so sess-abc can't pick up sess-abc-extra's log. Added a substring-exclusion test.
  4. Empty projection leaves a stale _meta slot (claudeAgentSession.ts) — the autorun now lets withSessionDebugArtifacts([]) clear the slot (it removes only this slot, preserving git/github), skipping just the initial nothing-to-nothing write.

The Linux / Electron check failure was an unrelated flake — McpStdioStateHandler > sigterm after grace (a timing test expecting SIGTERM received after a grace timer, which hadn't fired). macOS / Electron, Linux / Browser, and Compile & Hygiene all passed; the new push re-runs it.

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Base: f236d934 Current: 6dca0f07

No screenshot changes.

TylerLeonhardt and others added 2 commits July 24, 2026 13:59
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>
TylerLeonhardt and others added 2 commits July 24, 2026 14:31
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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: parseRemoteAuthorityFromScheme only accepts remote-<authority>-copilotcli (see copilotCliEventsUri.ts:85-92). In the workbench action this makes activeSession undefined, so collectAgentHostDebugLogs never queries the remote Claude host's _meta. Use the provider-neutral isRemoteAgentHostSessionType check; authority resolution is already handled later by getRemoteConnectionForSession.
 * `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

Comment on lines +241 to +243
const resource = activeSession?.isLocal
? URI.file(artifact.path)
: artifactAuthority ? toAgentHostUri(URI.from({ scheme: Schemas.file, path: artifact.path }), artifactAuthority) : undefined;
Comment on lines +248 to +252
// 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>
@TylerLeonhardt

Copy link
Copy Markdown
Member Author

Addressed the two new review comments in e7a7618:

  • Windows host path breaks URI wrapping (line 243) — the host now encodes each artifact as a file:// URI string rather than a raw fsPath (it owns its own drive/UNC/separator semantics). The client just URI.parses it and, for a remote session, wraps it for the connection authority — so a Windows host's C:\… no longer needs client-side URI.file/URI.from. Renamed IDebugArtifact.pathuri.
  • length: size doesn't bound the remote read (line 252) — correct: the AHP filesystem provider advertises only FileReadWrite (no FileReadStream), so readFileStream({length}) falls back to a whole-file readFile. Replaced it with a stat-size cap in createDebugLogFile: above 50 MiB a remote --debug log is exported as a short "[skipped]" note instead of being fetched and re-buffered in the renderer.

(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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants