Describe the bug
A persisted Copilot CLI session can become permanently unresumable when code-change counters in a file-editing tool's toolTelemetry.metrics are stored as masked strings instead of numbers.
Observed behavior on a programmatic CLI session:
- A file-editing tool completes with nonnegative integer
linesAdded / linesRemoved metrics.
- The post-tool hook receives those integer values and reports that it did not modify the result.
- The persisted
tool.execution_complete event contains a string such as "******" for one or both counters.
- The session usage tracker aggregates the value with
+= without validating its type. JavaScript then concatenates the string into the session total.
session.shutdown.data.codeChanges.linesAdded or linesRemoved is persisted as a string.
- The next
session.resume rejects events.jsonl because ShutdownCodeChanges requires an integer (expected i64).
The session's conversation and tool results remain present, but the entire session can no longer be resumed because of diagnostic usage metadata.
Affected versions
@github/copilot: 1.0.73 (package build commit 98003c0)
@github/copilot-sdk: 1.0.7
- Runtime: Linux container, Node.js 20
Why this appears to happen
The external tool result contract permits opaque JSON under toolTelemetry. The shutdown event contract is stricter and requires nonnegative integers for codeChanges.linesAdded and linesRemoved.
The 1.0.73 runtime's usage aggregation effectively performs:
aggregate.linesAdded += toolTelemetry.metrics.linesAdded ?? 0;
aggregate.linesRemoved += toolTelemetry.metrics.linesRemoved ?? 0;
If a persisted tool event contains a masked string, numeric addition becomes string concatenation. The resulting shutdown record violates the runtime's own resume schema.
The exact component that replaces the original integer with the masked string appears to be between post-tool-hook completion and persisted tool.execution_complete event creation. The tool result itself and the unmodified post-tool-hook input contain integers.
Expected behavior
linesAdded and linesRemoved remain nonnegative integers throughout tool-event persistence.
- Invalid diagnostic metrics never make the conversation unresumable.
- Resume tolerates or repairs malformed diagnostic-only usage fields rather than rejecting the complete session.
Suggested fixes
- Validate code-change counters before writing
tool.execution_complete.
- In usage aggregation, add values only when
Number.isSafeInteger(value) && value >= 0.
- During event replay, ignore malformed code-change metrics instead of concatenating them.
- Before writing
session.shutdown, assert that aggregate counters satisfy ShutdownCodeChanges.
- Add a backward-compatible repair path for already persisted sessions, treating unrecoverable masked counters as zero or recomputing them from valid events.
- Consider skip-and-warn behavior for schema-invalid diagnostic fields so one metric cannot invalidate a complete session.
Workaround
With the CLI stopped, back up events.jsonl, then atomically sanitize both locations while preserving one JSON object per LF-terminated line:
- In
tool.execution_complete, replace non-integer or negative toolTelemetry.metrics.linesAdded / linesRemoved with 0 (or remove those metric keys).
- In
session.shutdown, replace invalid codeChanges.linesAdded / linesRemoved with 0, or recompute them from valid tool events.
Both locations must be repaired; fixing only the shutdown record can allow replay of an earlier malformed tool event to poison a later shutdown again.
Related issues
This appears to be another writer-versus-validator mismatch in the same family as:
No raw session logs, user content, or internal identifiers are included in this report.
Describe the bug
A persisted Copilot CLI session can become permanently unresumable when code-change counters in a file-editing tool's
toolTelemetry.metricsare stored as masked strings instead of numbers.Observed behavior on a programmatic CLI session:
linesAdded/linesRemovedmetrics.tool.execution_completeevent contains a string such as"******"for one or both counters.+=without validating its type. JavaScript then concatenates the string into the session total.session.shutdown.data.codeChanges.linesAddedorlinesRemovedis persisted as a string.session.resumerejectsevents.jsonlbecauseShutdownCodeChangesrequires an integer (expected i64).The session's conversation and tool results remain present, but the entire session can no longer be resumed because of diagnostic usage metadata.
Affected versions
@github/copilot: 1.0.73 (package build commit98003c0)@github/copilot-sdk: 1.0.7Why this appears to happen
The external tool result contract permits opaque JSON under
toolTelemetry. The shutdown event contract is stricter and requires nonnegative integers forcodeChanges.linesAddedandlinesRemoved.The 1.0.73 runtime's usage aggregation effectively performs:
If a persisted tool event contains a masked string, numeric addition becomes string concatenation. The resulting shutdown record violates the runtime's own resume schema.
The exact component that replaces the original integer with the masked string appears to be between post-tool-hook completion and persisted
tool.execution_completeevent creation. The tool result itself and the unmodified post-tool-hook input contain integers.Expected behavior
linesAddedandlinesRemovedremain nonnegative integers throughout tool-event persistence.Suggested fixes
tool.execution_complete.Number.isSafeInteger(value) && value >= 0.session.shutdown, assert that aggregate counters satisfyShutdownCodeChanges.Workaround
With the CLI stopped, back up
events.jsonl, then atomically sanitize both locations while preserving one JSON object per LF-terminated line:tool.execution_complete, replace non-integer or negativetoolTelemetry.metrics.linesAdded/linesRemovedwith0(or remove those metric keys).session.shutdown, replace invalidcodeChanges.linesAdded/linesRemovedwith0, or recompute them from valid tool events.Both locations must be repaired; fixing only the shutdown record can allow replay of an earlier malformed tool event to poison a later shutdown again.
Related issues
This appears to be another writer-versus-validator mismatch in the same family as:
data.kind.exitCode: Number must be greater than or equal to 0#3454 - valid Windows exit code rejected by an overly strict schematokensRemovedmade sessions unresumableNo raw session logs, user content, or internal identifiers are included in this report.