Skip to content

fix: persist chat editing timeline telemetryInfo so sessionResource survives reload (fixes #326994)#326995

Open
vs-code-engineering[bot] wants to merge 1 commit into
mainfrom
fix/chat-editing-timeline-telemetry-sessionresource-50546a799d6cf23e
Open

fix: persist chat editing timeline telemetryInfo so sessionResource survives reload (fixes #326994)#326995
vs-code-engineering[bot] wants to merge 1 commit into
mainfrom
fix/chat-editing-timeline-telemetry-sessionresource-50546a799d6cf23e

Conversation

@vs-code-engineering

Copy link
Copy Markdown
Contributor

Summary

An uncaught TypeError: Cannot read properties of undefined (reading 'toString') fires in the chat-editing telemetry path (2500 hits / 965 users on v1.129.1). The crash happens in ChatModelStore.get when it calls uri.toString() on an undefined URI. The undefined originates from a IModifiedEntryTelemetryInfo.sessionResource field (declared non-nullable URI) that becomes undefined after a chat editing session is persisted and restored.

The producer is ChatEditingSession._getTelemetryInfoForModel, which returns an object whose fields are implemented as prototype getters. When the checkpoint timeline is persisted with JSON.stringify, prototype getters are not own enumerable properties and are silently dropped, so each persisted telemetryInfo becomes {}. After reload, entries reconstructed from the timeline carry sessionResource: undefined, which later crashes the telemetry pipeline.

Fixes #326994
Recommended reviewer: @connor4312

Culprit Commit

Not pinpointed to a single SHA (git history unavailable in this environment). The regression window (first seen 2026-07-15, v1.129.0-insider) corresponds to the introduction of the getter-based _getTelemetryInfoForModel object (comment: "Make these getters because the response result is not available when the file first starts to be edited") combined with the checkpoint-timeline persistence path that serializes telemetryInfo verbatim. The timeline persistence/replay code is owned by @connor4312 (checkpoint timeline, epoch replay, edit restore).

Code Flow

flowchart TD
    A["_getTelemetryInfoForModel<br/>returns object with prototype getters<br/>chatEditingSession.ts:1035"] --> B["FileOperation / IFileBaseline<br/>hold live telemetryInfo"]
    B --> C["timeline.getStateForPersistence()<br/>chatEditingCheckpointTimelineImpl.ts:444"]
    C --> D["storeState: JSON.stringify(data)<br/>getters dropped -> telemetryInfo = {}<br/>chatEditingSessionStorage.ts:191,197"]
    D --> E["restoreState: revive(data.timeline)<br/>telemetryInfo still {} -> sessionResource undefined<br/>chatEditingSessionStorage.ts:114"]
    E --> F["_reconstructFileState -> setContents<br/>entry gets _telemetryInfo with undefined sessionResource<br/>chatEditingCheckpointTimelineImpl.ts:475"]
    F --> G["_notifySessionAction reads<br/>_telemetryInfo.sessionResource (undefined)<br/>chatEditingModifiedFileEntry.ts:272,316"]
    G --> H["notifyUserAction:<br/>_sessionModels.get(action.sessionResource)<br/>chatServiceImpl.ts:377"]
    H --> I["ChatModelStore.get(undefined)<br/>toKey: uri.toString() -> TypeError<br/>chatModelStore.ts:99,246"]
Loading

Affected Files

  • src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts_getTelemetryInfoForModel (the producer). Modified.
  • src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCheckpointTimelineImpl.ts — persists/replays telemetryInfo (context, unchanged).
  • src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSessionStorage.tsJSON.stringify/revive of the timeline (context, unchanged).
  • src/vs/workbench/contrib/chat/common/model/chatModelStore.ts — crash site get/toKey (context, unchanged; intentionally not patched).

Repro Steps

  1. Start a chat editing session and let the agent modify a file (creates timeline operations/baselines whose telemetryInfo is the getter-based object).
  2. Persist the session (storeState) and reload the window so the session is restored from disk (restoreStaterevive(data.timeline)).
  3. Manually edit the AI-edited file. After ~1s the RunOnceScheduler in chatEditingModifiedFileEntry fires _notifySessionAction('userModified').
  4. _telemetryInfo.sessionResource is undefined (lost during serialization) → ChatModelStore.get(undefined)TypeError: Cannot read properties of undefined (reading 'toString').

How the Fix Works

Chosen approach (chatEditingSession.ts): add a toJSON() method to the anonymous IModifiedEntryTelemetryInfo class returned by _getTelemetryInfoForModel. toJSON() snapshots each getter value into a plain object with own enumerable properties (agentId, modelId, modeId, command, sessionResource, requestId, applyCodeBlockSuggestionId, feature; result is set to undefined, matching the snapshot-entry serialization which also drops result). JSON.stringify honors toJSON, so the fields — including sessionResource as a serialized URI — now survive persistence and are correctly revived by revive(data.timeline).

This is a fix at the data producer, not at the crash site: the object that enters the persistence pipeline is made serializable, rather than adding a null-guard where the undefined finally surfaces. Live consumers are unaffected because they read the fields via normal property access, which still hits the getters; only JSON.stringify uses toJSON. It also restores the other telemetry fields (requestId, agentId, etc.) that were silently lost from persisted timelines by the same getter-serialization gap.

After this change, _getTelemetryInfoForModel (chatEditingSession.ts:1035) cannot produce a telemetryInfo whose persisted form lacks sessionResource, because toJSON emits sessionResource as an own enumerable property that JSON.stringify serializes and revive restores.

Alternatives considered:

  • Explicitly serialize/deserialize telemetryInfo inside the timeline persistence (a dedicated DTO like the snapshot-entry path): rejected as more invasive (new DTO + revive plumbing in the timeline) while addressing the same root cause less directly.
  • Converting the getters to eager own properties: rejected because the getters exist intentionally so late-available fields (e.g. result) reflect the current response state during live use.
  • Guarding sessionResource/toKey at the crash site or in notifyUserAction: rejected because it hides the bug at the consumer instead of fixing the data producer, and the telemetry pipeline depends on real values.

Recommended Owner

@connor4312 — owns the chat-editing checkpoint timeline persistence and edit-restore code path (authored the timeline epoch/replay/restore logic in chatEditingCheckpointTimelineImpl.ts), which is where the getter-based telemetryInfo is serialized and lost.

Generated by errors-fix · 3.8K AIC · ⌖ 34.9 AIC · ⊞ 71K ·

…urvives reload (fixes #326994)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 22, 2026 15:56

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

Note

Copilot could not run the full agentic suite for this review because it was automatically requested on a bot-authored pull request. Request a review from Copilot under Reviewers to retry with the full agentic suite. Improved support for bot-authored pull requests is coming soon.

Adds explicit JSON serialization for modified-entry telemetry info so values provided by prototype getters are preserved when persisted in the checkpoint timeline.

Changes:

  • Implemented toJSON() to snapshot getter-backed fields into a plain object for JSON.stringify.
  • Ensures sessionResource (and related fields) survive serialization and later revival.

Comment on lines +1060 to +1072
toJSON(): IModifiedEntryTelemetryInfo {
return {
agentId: this.agentId,
modelId: this.modelId,
modeId: this.modeId,
command: this.command,
sessionResource: this.sessionResource,
requestId: this.requestId,
result: undefined,
applyCodeBlockSuggestionId: this.applyCodeBlockSuggestionId,
feature: this.feature,
};
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

result: undefined here is intentional and mirrors the existing checkpoint-timeline serialization: chatEditingSessionStorage.ts (the snapshot-entry telemetryInfo DTO) also sets result: undefined. The IChatAgentResult is deliberately not persisted — it isn't needed for the telemetry path this PR fixes (the crash is caused by the missing sessionResource, not result), and serializing it would bloat the stored timeline. JSON.stringify dropping the key is the expected/consistent behavior, and consumers already treat result as undefined after revival. Serializing the live value would go beyond the scope of #326994 (restoring sessionResource), so I'm leaving this as-is to stay consistent with the established pattern.

Generated by errors-fix-driver · 678.2 AIC · ⌖ 25.5 AIC · ⊞ 72.9K

Comment on lines +1056 to +1072
// The fields above are getters on the prototype, so they are NOT own enumerable
// properties and would be dropped by `JSON.stringify` when this object is persisted
// as part of the checkpoint timeline. Snapshot the current values into a plain object
// so `sessionResource` (and the other fields) survive serialization and revival.
toJSON(): IModifiedEntryTelemetryInfo {
return {
agentId: this.agentId,
modelId: this.modelId,
modeId: this.modeId,
command: this.command,
sessionResource: this.sessionResource,
requestId: this.requestId,
result: undefined,
applyCodeBlockSuggestionId: this.applyCodeBlockSuggestionId,
feature: this.feature,
};
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion. This PR's scope is bounded by #326994: restore the sessionResource field that is lost when the checkpoint timeline's telemetryInfo is serialized. A maintainability refactor to centralize the serialized-key list / add an exhaustiveness check is a reasonable improvement but falls outside that scope, so I'm leaving it for a separate follow-up. Note the explicit field mapping also intentionally mirrors the existing per-field DTO in chatEditingSessionStorage.ts. Feel free to open a tracking issue if you'd like the shared-key-list idea addressed.

Generated by errors-fix-driver · 678.2 AIC · ⌖ 25.5 AIC · ⊞ 72.9K

@vs-code-engineering
vs-code-engineering Bot marked this pull request as ready for review July 22, 2026 15:58
@vs-code-engineering
vs-code-engineering Bot enabled auto-merge (squash) July 22, 2026 15:58

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

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment on lines +1060 to +1072
toJSON(): IModifiedEntryTelemetryInfo {
return {
agentId: this.agentId,
modelId: this.modelId,
modeId: this.modeId,
command: this.command,
sessionResource: this.sessionResource,
requestId: this.requestId,
result: undefined,
applyCodeBlockSuggestionId: this.applyCodeBlockSuggestionId,
feature: this.feature,
};
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed that result: undefined is omitted by JSON.stringify — that's intentional. It matches the existing snapshot-entry serialization in chatEditingSessionStorage.ts, which also emits result: undefined, and the revive path/consumers already treat result as optional/undefined. Persisting the real IChatAgentResult isn't required to fix #326994 (which is about sessionResource being lost) and would enlarge the stored timeline, so I'm keeping the behavior consistent with the surrounding code rather than changing the persisted shape in this PR.

Generated by errors-fix-driver · 678.2 AIC · ⌖ 25.5 AIC · ⊞ 72.9K

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Error] unhandlederror-Uncaught TypeError: Cannot read properties of undefined (reading 'toString')

2 participants