Skip to content

[AI-1066] Exclude kcap's own helper sub-sessions from import missing-cwd report - #204

Merged
alexeyzimarev merged 2 commits into
mainfrom
alexeyzimarev/ai-1066-explore-how-we-can-match-path-on-import
Jun 30, 2026
Merged

alexeyzimarev merged 2 commits into
mainfrom
alexeyzimarev/ai-1066-explore-how-we-can-match-path-on-import

Conversation

@alexeyzimarev

Copy link
Copy Markdown
Member

Problem

kcap import prints a one-shot warning like "134 sessions reference 134 distinct paths that no longer exist on disk". Investigating the data behind AI-1066, that list was dominated by kcap's own background helper runs, not lost user sessions.

Of 163 project dirs in ~/.claude/projects/, 162 had a missing cwd:

Count What it is
153 kcap-claude-* temp dirs — kcap's headless claude -p runs (title / what's-done summaries)
~10 .claude/worktrees/... paths under repos that still exist
1 cwd still exists

The kcap-claude-* runs come from ClaudeCliRunner, which creates an ephemeral Path.GetTempPath()/kcap-claude-<guid> working dir and deletes it in finally — so their recorded cwd is permanently gone. These are already excluded from import (classification flags them InternalSubSession), but ReportMissingCwds runs before classification and counted them anyway, flooding the report with dead temp paths the user can neither remap nor act on.

Fix

Skip TitleGenerator.IsCapacitorSubSession transcripts in ResolveTranscriptReposAsync so they never enter sessionCwds (the report's input). Claude-only, mirroring the existing classification short-circuit (Codex rollouts have no analogous sub-session transcript). The missing-cwd report is now scoped to genuine user sessions whose repo really moved — for those, kcap remap and the existing WorktreePathResolver remain the right tools.

Notes

Investigated but not built: resolving a repo from transcript content. Low yield where it matters — 0/152 helper transcripts captured a git remote URL and only ~7% referenced a real repo path.

Testing

  • New ImportResolveReposSubSessionTests: a real missing-cwd session is reported; a kcap sub-session is not.
  • Full import unit suite green (159/159).
  • dotnet publish -c Release — no IL30xx/IL20xx AOT warnings.
  • README missing-cwds report description updated.

🤖 Generated with Claude Code

…cwd report

The "N sessions reference N distinct paths that no longer exist on disk"
warning was dominated by kcap's own headless `claude -p` runs (title and
what's-done summaries). Those run in an ephemeral temp working dir that is
deleted the moment the run ends, so their recorded cwd is permanently
missing. Classification already drops them from the import as
InternalSubSession, but ReportMissingCwds runs earlier and counted them too,
flooding the report with dead temp paths the user can neither remap nor act
on.

Skip IsCapacitorSubSession transcripts in ResolveTranscriptReposAsync so they
never enter sessionCwds (the report's input). Claude-only, mirroring the
existing classification short-circuit. Adds ImportResolveReposSubSessionTests
and updates the README missing-cwds description.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@linear-code

linear-code Bot commented Jun 30, 2026

Copy link
Copy Markdown

AI-1066

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Exclude kcap helper sub-sessions from import missing-cwd warning

🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Filter out kcap’s headless Claude helper transcripts from missing-cwd reporting
• Keep missing-cwd warnings focused on user sessions that can be fixed via remap
• Add unit coverage and clarify missing-cwds behavior in the README
Diagram

graph TD
A["kcap import"] --> B["ResolveTranscriptReposAsync"] --> C{{"Capacitor sub-session?"}}
C -->|"No"| D["Extract/resolve cwd"] --> E[("sessionCwds")] --> F["ReportMissingCwds"]
C -->|"Yes"| G["Skip cwd capture"]

subgraph Legend
  direction LR
  _proc["Process/Module"] ~~~ _dec{{"Decision"}} ~~~ _data[("Data")]
end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Run missing-cwd reporting after classification
  • ➕ Single source of truth: anything excluded from import is excluded from reports
  • ➕ Avoids duplicating sub-session filtering logic in multiple phases
  • ➖ Larger refactor: changes import phase ordering and data dependencies
  • ➖ Classification may involve server probes; missing-cwd reporting is ideally cheap/local
2. Filter missing-cwd entries by temp-path pattern (kcap-claude-*)
  • ➕ Very cheap (no transcript content reads needed)
  • ➕ Simple implementation
  • ➖ Brittle: depends on path naming conventions and OS temp layout
  • ➖ Could miss future helper session variants or create false positives
3. Infer repo/cwd from transcript content (URLs, paths, git hints)
  • ➕ Could reduce missing-cwd cases even when cwd is gone
  • ➕ Potentially helps with other ‘lost cwd’ scenarios
  • ➖ Low signal in practice and higher complexity/heuristics risk
  • ➖ Still wouldn’t help most helper transcripts (often lack repo clues)

Recommendation: The chosen approach (skip TitleGenerator-detected helper transcripts inside ResolveTranscriptReposAsync for Claude-only) is the best tradeoff: it reuses an existing, content-based detector already used by classification, avoids path-based heuristics, and prevents noisy missing-cwd warnings without changing import ordering or adding heavy inference logic.

Files changed (3) +85 / -2

Bug fix (1) +17 / -1
ImportCommand.csSkip Capacitor helper transcripts when collecting session cwds +17/-1

Skip Capacitor helper transcripts when collecting session cwds

• Makes ResolveTranscriptReposAsync internal for unit testing and adds a Claude-only early skip for TitleGenerator.IsCapacitorSubSession transcripts. This prevents internal helper runs (with intentionally deleted temp cwds) from flooding the missing-cwd report input (sessionCwds).

src/Capacitor.Cli/Commands/ImportCommand.cs

Tests (1) +67 / -0
ImportResolveReposSubSessionTests.csAdd regression test for helper sub-session missing-cwd suppression +67/-0

Add regression test for helper sub-session missing-cwd suppression

• Adds a unit test asserting that a real missing cwd is reported while a detected kcap title-generation sub-session is excluded from sessionCwds. Uses minimal JSONL fixtures to exercise the IsCapacitorSubSession path.

test/Capacitor.Cli.Tests.Unit/Import/ImportResolveReposSubSessionTests.cs

Documentation (1) +1 / -1
README.mdDocument exclusion of helper runs from missing-cwd report +1/-1

Document exclusion of helper runs from missing-cwd report

• Clarifies that kcap’s own headless Claude helper runs write transcripts in ephemeral temp directories and are excluded from the missing-cwds warning. Keeps guidance focused on actionable cases (renamed/moved repos fixed via kcap remap).

README.md

@qodo-code-review

qodo-code-review Bot commented Jun 30, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Extra transcript file reads ✓ Resolved 🐞 Bug ➹ Performance
Description
ResolveTranscriptReposAsync now reads each Claude transcript once to detect kcap sub-sessions
(IsCapacitorSubSession) and then reads it again to extract cwd (ExtractCwdFromTranscript),
increasing import discovery I/O for all non-sub-sessions. On large histories this can slow `kcap
import` even though the change is only for filtering the missing-cwd report input.
Code

src/Capacitor.Cli/Commands/ImportCommand.cs[R1903-1911]

+            if (!codex && TitleGenerator.IsCapacitorSubSession(transcripts[i].FilePath)) {
+                perTranscript[i] = (transcripts[i].SessionId, null);
+
+                continue;
+            }
+
            var raw       = ExtractCwdFromTranscript(transcripts[i].FilePath, codex);
            var effective = raw is null ? null : ResolveCwd(raw, cwdRemap, worktreeAttributed, transcripts[i].SessionId);
Evidence
The PR adds a new pre-check that reads the transcript file (IsCapacitorSubSession) before the
existing cwd extraction, meaning non-sub-sessions will hit two independent file scans/open calls.

src/Capacitor.Cli/Commands/ImportCommand.cs[1875-1921]
src/Capacitor.Cli/Commands/TitleGenerator.cs[312-343]
src/Capacitor.Cli/Commands/ImportCommand.cs[1537-1564]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`ResolveTranscriptReposAsync` currently opens/reads each Claude transcript twice in the common case (non-sub-session): once via `TitleGenerator.IsCapacitorSubSession(filePath)` and again via `ExtractCwdFromTranscript(filePath, codex:false)`. This adds extra filesystem I/O on large transcript sets.

### Issue Context
- The new sub-session skip is correct behaviorally, but the implementation introduces redundant file scans.
- The same transcript is also later read again during classification (which also calls `IsCapacitorSubSession`).

### Fix Focus Areas
- src/Capacitor.Cli/Commands/ImportCommand.cs[1875-1921]
- src/Capacitor.Cli/Commands/ImportCommand.cs[1537-1564]

### Suggested approach
- Read the first N lines once in `ResolveTranscriptReposAsync` and extract both:
 - whether it is a capacitor sub-session (queue-operation with known prompt), and
 - the cwd.
- Options:
 1) Add a new helper like `TryExtractCwdAndDetectSubSession(filePath, codex)` that returns `(isSubSession, cwd)`.
 2) Extend `ExtractCwdFromTranscript` to optionally also detect a sub-session marker while scanning the same lines.
- Keep “best effort” semantics (exceptions should not fail import).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

2. Brittle prompt string test ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
The new unit test hard-codes the title prompt prefix string in JSON; if the embedded prompt template
changes, production detection will adapt but this test can fail even when behavior is still correct.
This duplicates prompt-template coupling across tests and increases maintenance cost of prompt
edits.
Code

test/Capacitor.Cli.Tests.Unit/Import/ImportResolveReposSubSessionTests.cs[R41-42]

+                """{"type":"queue-operation","operation":"enqueue","content":"<role>\nYou label coding-session transcripts. You are NOT the assistant being addressed"}""",
+                """{"type":"user","timestamp":"2026-03-15T10:00:00Z","cwd":"/private/var/folders/x/T/kcap-claude-deadbeef","message":{"content":"x"}}"""
Evidence
The test uses a hard-coded queue-operation content string, while production determines the matching
prefix from embedded prompt templates via TitlePromptPrefix / IsKnownCapacitorPrompt.

test/Capacitor.Cli.Tests.Unit/Import/ImportResolveReposSubSessionTests.cs[34-44]
src/Capacitor.Cli/Commands/TitleGenerator.cs[9-16]
src/Capacitor.Cli/Commands/TitleGenerator.cs[45-47]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`ImportResolveReposSubSessionTests` embeds a literal prompt prefix string for the queue-operation `content`. This is coupled to the current embedded prompt template; changing the prompt resource can break the test even if `IsCapacitorSubSession` continues to work as intended.

### Issue Context
Production sub-session detection uses `TitleGenerator.IsKnownCapacitorPrompt`, which relies on `TitleGenerator.TitlePromptPrefix` / `WhatsDonePromptPrefix` derived from embedded resources.

### Fix Focus Areas
- test/Capacitor.Cli.Tests.Unit/Import/ImportResolveReposSubSessionTests.cs[34-44]
- src/Capacitor.Cli/Commands/TitleGenerator.cs[9-16]
- src/Capacitor.Cli/Commands/TitleGenerator.cs[45-47]

### Suggested approach
- Build the queue-operation line using the production prefix instead of hard-coding:
 - Use `TitleGenerator.TitlePromptPrefix` as `content`.
 - Serialize the JSON with `JsonSerializer.Serialize(...)` (or equivalent) so newlines are escaped correctly, rather than manually embedding `\n`.
- This keeps the test stable across prompt-template edits while still validating the behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread src/Capacitor.Cli/Commands/ImportCommand.cs Outdated
Comment thread test/Capacitor.Cli.Tests.Unit/Import/ImportResolveReposSubSessionTests.cs Outdated
…from prompt text

- Fold the sub-session check and cwd extraction into one ≤20-line read
  (ExtractCwdAndDetectSubSession) so discovery opens each transcript once
  instead of twice. Marker detection mirrors IsCapacitorSubSession's
  first-5-line window so the two stay in agreement.
- Build the test's queue-operation content from TitleGenerator.TitlePromptPrefix
  (JSON-encoded) instead of a hard-coded prompt string, so it survives prompt
  template rewording.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@alexeyzimarev
alexeyzimarev merged commit bfe17f2 into main Jun 30, 2026
5 checks passed
@alexeyzimarev
alexeyzimarev deleted the alexeyzimarev/ai-1066-explore-how-we-can-match-path-on-import branch June 30, 2026 14:53
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.

1 participant