test: cover the Copilot CLI session source#32
Draft
ohad6k wants to merge 1 commit into
Draft
Conversation
The Copilot CLI adapter was the only session source with no dedicated test file. Add tests/test_copilot_source.py covering the three places it can regress: discovery of ~/.copilot/session-state/<id>/events.jsonl, extraction of data.content from user.message events, and the data.source == "system" filter. Fixtures mirror the real on-disk shape (compact JSON lines, ISO timestamps, transformedContent alongside content, workspace.yaml and checkpoints/ sidecars). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every session source in emulo has a test file named after it except one.
test_codex_control_envelopes.py,test_claude_human_turns.py,test_opencode_source.py,test_antigravity_source.py— and nothing anywhere in the suite touched the Copilot CLI adapter. This addstests/test_copilot_source.py. No production code changes.What the adapter does
SOURCES["copilot"]points at~/.copilot/session-state. Copilot CLI writes oneevents.jsonlper session undersession-state/<session-id>/, next to non-transcript state (workspace.yaml,checkpoints/,files/,research/).discover_files()globs**/*.jsonland then drops any path with asubagentscomponent.user_messages()gates on the raw-line marker"type":"user.message", then keepsdata.contentand skips events whosedata.source == "system"(harness steering). The date comes fromtimestamp[:10].session_label()prefixes the parent directory, because every Copilot transcript is namedevents.jsonland the bare filename would collapse every session block header to the same string.Fixtures are built from the real shape on disk: compact JSON lines, ISO-8601
timestamp, anddatacarryingcontent,transformedContent,attachments,delivery,agentMode.What each test protects
Discovery
test_discovers_one_events_file_per_session_and_ignores_sidecars— two sessions produce exactly two entries;workspace.yamlandcheckpoints/cp1.jsonstay out.test_subagents_transcripts_are_dropped_by_path_component— an agent-to-agent transcript undersubagents/is dropped, while a directory namedmy-subagents-notes/still mines. Catches a substring-vs-component regression in the filter.test_missing_or_empty_root_is_quiet— a missing or empty root returns[]instead of raising.test_registered_in_sources_under_dot_copilot— the root stays~/.copilot/session-state;source_kind()routes on that path component, so moving it silently reclassifies every session ascustom.Extraction
test_mines_typed_content_not_the_harness_expansion— asserts the mined text isdata.content, notdata.transformedContent. On real logstransformedContentis the model-facing string: the typed prose wrapped in<current_datetime>and<system_reminder>blocks (734 chars of typed text became 1135 chars in one real record). Switching fields would pour harness injections into the corpus.test_compact_and_spaced_serialization_both_mine— the line-marker fast path matches raw text beforejson.loads, so both"type":"user.message"(what Copilot actually writes) and the spaced form must survive it. GuardsUSER_LINE_MARKERS.test_system_source_events_are_dropped_and_typed_turns_kept—source: "system"is dropped;source: "user"and an absentsourceare both kept. The absent case matters: on the Copilot CLI build I checked locally,user.messagerecords carry nosourcekey at all, so an over-eager filter (e.g. requiringsource == "user") would mine zero turns from current logs.test_non_user_event_types_are_ignored—session.info,assistant.message,tool.execution_start,system.message,subagent.startedall carrydata.content-ish payloads and must not be mined.test_malformed_line_is_skipped_and_later_turns_survive— a torn write, a blank line and a non-JSON line are skipped per line, and a turn written after them is still mined.test_empty_session_yields_nothing_and_is_not_counted— an empty file and a session with no user turns both yield[], andmine_files()counts 0 sessions / 0 messages rather than emitting an empty block.test_injected_context_and_pasted_logs_are_not_mined— a# AGENTS.md instructionsprompt, a pasted traceback and a whitespace-only turn drop; the real turn survives.Identity and pipeline
test_source_kind_and_per_session_label—source_kindiscopilot, the label is<session-id>/events.jsonl, and two sessions get distinct labels and distinctstable_session_ids.test_mine_files_tags_records_as_copilot_with_real_dates— end to end: the record is taggedsource:copilot, first/last dates come from the event timestamps, and the system-injected turn is absent from the block text.test_redaction_runs_on_copilot_turns— aghp_token in a Copilot turn is redacted and counted.Verification
14 tests added by this file, 410 total, all green. The 3 skips are pre-existing.
Found, not fixed
user_messages()wraps the whole file read in onetry/except Exception: pass, so a single record with an unexpected field type does not skip that record — it silently ends mining for the rest of the session. Malformed JSON is handled correctly per line (continue), but a parseable record with a bad type is not. Reproduced against the current code with a three-turn file that returned only the first turn, no warning:{"type":"user.message","data":null,...}—o.get("data", {})returnsNonebecause the key exists,.getraisesAttributeError, and every later turn in that file is lost.data.contentas a list —.strip()on a list raises, same truncation.timestamp(epoch ms instead of ISO) —[:10]on an int raises, same truncation.None of these shapes appear in the Copilot logs I have locally, so this is latent rather than active, but the same handler covers every source, so any producer that changes a field type would quietly halve someone's corpus. I did not write tests asserting the current truncation behavior, since that would pin a bug in place. A fix would be a per-record
try/exceptinside the loop plusisinstanceguards ondata,contentandtimestamp.🤖 Generated with Claude Code