Skip to content

fix(session-flow): classify automated, Desktop and orphaned sessions in audit-sessions #6076

Closed

Description

@kyle-sexton

Part of #6074

Paths below are relative to plugins/session-flow/skills/audit-sessions/ unless they start with plugins/. Line numbers are on origin/main 9671ece.

Problem

Four counting and population defects:

E4. Automated and sandbox sessions sit in the baseline and the medians (medium). scripts/collect.py:779 ingests every project (for main in sorted(root.glob("*/*.jsonl")):). scripts/sweep.py:119-120 takes medians over every record, and drift runs before the scope and window filters (sweep.py:332-341). Neither script reads the transcript's entrypoint field.

E6. tools.interrupts flags any single occurrence (low). reference/sweep-rules.json:77-86 sets the threshold to 0 and scripts/sweep.py:131 flags v > threshold, so one interrupt is a finding. plugins/session-flow/scripts/transcript_reader.py:69 matches both [Request interrupted by user] and ... for tool use]; collect.py:364-374 counts it, and one user rejection can also land in tools.denials and tools.user_rejected.

E7. Claude Desktop prompts typed by the person are not counted as typed turns (low). transcript_reader.py:188-190 returns False when promptSource is set and not in {"typed", "queued"} (:70), even after origin.kind is human (:185-187). Desktop records carry promptSource: sdk. The injected-prefix list (:58, which includes <system-reminder>) then also rejects a Desktop prompt that begins with a reminder block.

I6. A set-aside orphaned transcript would be ingested as a phantom session (low, latent). collect.py:779 globs */*.jsonl and :638 uses main.stem as the session id, so <id>.orphaned-<timestamp>-<suffix>.jsonl becomes a second session.

Evidence

Verified this pass: the lines above at 9671ece.

Measured by the audit on one Windows 11 machine, 2026-10-03 (not re-measured here):

  • E4: 245 of 764 sessions are automated (120 in temp sandboxes, 125 headless entrypoint: sdk-cli outside temp); none has a typed turn. Pooled medians: tokens.main 23,820, tokens.sub 0, time.active_s 0, human.turns 1. Interactive-only medians: 79,380, 16,740, 1,174 and 2. promptSource cannot classify sessions, because Desktop typed prompts carry sdk (E7). The monitoring-usage docs list app.entrypoint ("How the session was launched, such as cli, sdk-cli, ..."); the transcript key itself is undocumented.
  • E6: 81 sessions flagged; 57 of them have exactly one interrupt. The built-in /insights counts the same markers (agreed in 197 of 200 joined sessions), so the operator chose to keep the definition and change only the threshold. The store's own p90 is 1; threshold 1 cuts 81 flagged sessions to 24.
  • E7: 7 user records in 4 Desktop transcripts carry origin.kind: human with promptSource: sdk (example text: "retry, keep using sonnet"). Without stripping a leading <system-reminder> block only 5 of the 7 are recovered. No CLI typed or queued record starts with a reminder (0 of 2,337), so CLI counts do not change. Desktop "runs the same underlying engine" per the desktop docs.
  • I6: reproduced on a scratch sandbox with the installed scripts: <id>.jsonl plus <id>.orphaned-<timestamp>-<suffix>.jsonl gave 2 records for 1 real session. https://code.claude.com/docs/en/claude-directory.md describes the file: "A previous transcript for the session that Claude Code set aside instead of overwriting or deleting it. It doesn't appear in the session picker". None existed on that machine.

Proposed approach

E4:

  1. Store every entrypoint value seen on each record (as cc_versions is stored, collect.py:643); a record whose transcript is gone is classed unknown. Add a census canary for the undocumented key. A cheaper first step needing no re-collect: classify temp-sandbox sessions from the stored cwd.
  2. Compute medians and findings over interactive sessions only and print how many automated sessions were set aside. Do not add an --include-automated flag. Optional: report the automated population as its own table.
  • Known limit, not solved: real work run headless is classed sdk-cli.

E6: change the tools.interrupts threshold from 0 to 1 and update its basis. The maintainer confirms the value. Not recommended (operator decision): counting only the bare interrupt form, which would diverge from /insights.

E7: in is_typed_turn, when origin.kind is human, accept the record regardless of promptSource, and strip one leading <system-reminder>...</system-reminder> block before the injected-prefix test. /session-flow:retro shares this reader and benefits.

I6: at collect.py:779, skip a transcript whose stem contains .orphaned-. Do not test for a UUID-shaped id (fixtures use ids such as gold-0001). Optional: a skip count in the collect summary.

Files: scripts/collect.py, scripts/sweep.py, scripts/census.py, reference/canaries.json, reference/sweep-rules.json, plugins/session-flow/scripts/transcript_reader.py, tests under scripts/tests/ (test_collect.py, test_sweep.py, test_transcript_reader.py), CHANGELOG and plugin.json.

Acceptance criteria

  • Store records carry the transcript's entrypoint value(s), or an explicit unknown.
  • A fixture store with one cli session and one sdk-cli session yields medians from the cli session only and reports 1 session set aside.
  • No flag outside SKILL.md's accepted argument set (SKILL.md:25-27) is added.
  • A session with exactly one interrupt produces no tools.interrupts finding; a session with two produces one.
  • A Desktop fixture user record with origin.kind: human and promptSource: sdk counts as a typed turn, including when it starts with a <system-reminder> block.
  • The existing test_transcript_reader.py cases for CLI records pass unchanged.
  • A fixture project holding <id>.jsonl and <id>.orphaned-<ts>-<suffix>.jsonl yields exactly one store record.

Constraints and gotchas

  • transcript_reader.py is shared with /session-flow:retro, and feat(session-flow): teach the transcript reader eight current record types #5931 (eight new record types) edits the same file; coordinate or rebase.
  • New store fields go within v1 (store-layout.md:58); do not redefine existing keys.
  • The baseline child computes its recheck-trigger p90 over the interactive population defined here, so land E4 first.
  • Version bump and CHANGELOG line per PR (AGENTS.md:20-22).

Context

Source: local handoff item 20261003-070338-session-flow-audit-sessions-accuracy-and-coverage.md (findings E4, E6, E7, I6). Related: #5931, #5818 (retro parser counting).

Activity

  1. added
    priority: mediumReal value, no hard deadline; normal backlog flow.
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.
    on Oct 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.priority: mediumReal value, no hard deadline; normal backlog flow.work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions