Skip to content

Cursor: attribute sessions to their repo + nest subagents under the parent - #242

Merged
alexeyzimarev merged 5 commits into
mainfrom
cursor-repo-attribution-and-subagents
Jul 3, 2026
Merged

alexeyzimarev merged 5 commits into
mainfrom
cursor-repo-attribution-and-subagents

Conversation

@realtonyyoung

Copy link
Copy Markdown
Collaborator

Fixes two Cursor ingest gaps. Pairs with the kcap-server PR (shared repo recorder + Cursor normalizer id-scoping for subsessions) — both halves ship together. Tracks Linear AI-1152 (repo attribution), AI-1153 (subagent nesting), and the subagent half of AI-1154 (backfill).

AI-1152 — repo attribution

Cursor sessions never grouped under a repository (only "All repos") because the sidebar keys on repo_hash, set only by a RepositoryDetected event, which needs a git-detected owner/repo_name. Every other vendor's hook path attaches a repository node via RepositoryDetection.EnrichWithRepositoryInfo — but that reads cwd, and Cursor payloads carry workspace_roots instead, and CursorHookCommand never called it.

  • Add RepositoryDetection.EnrichWithRepositoryInfoFromCwd + extract BuildRepositoryNode (shared).
  • CursorHookCommand enriches sessionStart from workspace_roots[0] (budget-bounded, fail-open).
  • CursorImportSource detects the repo from the workspace folder and attaches it to the synthetic sessionStart (also fixes historical import/backfill).

AI-1153 — nest subagents under the parent

A Cursor subagent (Task/Agent) runs as its own session/transcript with no explicit parent link — the only in-data signal is that the child's first user_query (minus the <user_query> wrapper) is byte-identical to the parent's Task input.prompt.

  • New pure CursorSubagentCorrelator.Correlate recovers the link by SHA-256 prompt hash (self-link guarded).
  • CursorImportSource.ClassifyAsync builds the map and stamps ParentSessionId/SubagentType; ImportSessionAsync routes a detected child through ImportSubagentAsync — /hooks/subagent-start (session_id=parent, agent_id=child) + transcript batches with agent_id=child (→ AgentSubsession-{parent}-{child}) + /hooks/subagent-stop — and emits no standalone session lifecycle. This mirrors how Gemini/OpenCode subagents nest, so no server read-model change is needed; the child never creates a top-level card.

Tests

CursorSubagentCorrelatorTests (3), CursorImportSourceTests incl. repo-attach + subagent-routing (34 total). AOT publish clean (no IL2026/IL3050).

🤖 Generated with Claude Code

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Cursor: attribute sessions to repos and nest subagent transcripts under parents

🐞 Bug fix ✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Enrich Cursor sessionStart with git repository metadata derived from workspace roots.
• Correlate Cursor subagent sessions to parents and ingest them as nested AgentSubsessions.
• Add unit tests covering repo attribution, subagent correlation, and subagent routing.
Diagram

graph TD
  A["CursorHookCommand"] -->|"sessionStart: add repository"| B["RepositoryDetection"] --> C[("kcap-server /hooks")]
  D["Cursor transcripts (jsonl)"] --> E["CursorImportSource"] -->|"session-start + transcript"| C
  E -->|"child routing"| C
  E --> F["CursorSubagentCorrelator"]
  E -->|"workspace repo"| B
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Server-side subagent correlation during normalization
  • ➕ No extra client/CLI logic; correlation evolves centrally
  • ➕ Can combine more signals (timestamps, tool_result metadata) as server model changes
  • ➖ Requires read-model/normalizer changes and careful backfill coordination
  • ➖ Harder to guarantee idempotency and avoid creating top-level cards for children
2. Correlation by timestamps/session directory proximity instead of prompt hash
  • ➕ Avoids prompt-collision risk and does not depend on exact prompt byte equality
  • ➖ Cursor transcripts lack reliable timestamps; heuristics are fragile across machines
  • ➖ Higher false-positive/false-negative rate than deterministic prompt matching
3. Add explicit parent_session_id to Cursor payloads (if/when Cursor supports it)
  • ➕ Most reliable linkage; avoids heuristic correlation entirely
  • ➖ Not currently available; blocks fixing ingestion gaps now
  • ➖ Still needs a migration/backfill story for historical data

Recommendation: Keep the PR’s approach: attaching a repository node at sessionStart matches other vendors’ ingestion expectations and fixes both live and backfill attribution without server changes. For subagents, prompt-hash correlation is the most deterministic available signal in Cursor data, and routing correlated children through subagent-start/transcript(agent_id)/subagent-stop aligns with existing server semantics so children never become top-level sessions.

Files changed (6) +548 / -21

Enhancement (2) +226 / -4
CursorImportSource.csAttach repository on import and nest correlated subagent sessions under parent +104/-4

Attach repository on import and nest correlated subagent sessions under parent

• During classification, computes child→parent subagent links and stamps ParentSessionId/SubagentType into SourceMeta. During import, routes correlated children through subagent-start → transcript batches with agent_id → subagent-stop (no standalone lifecycle) and enriches synthetic sessionStart with a repository node detected from the workspace folder (fail-open).

src/Capacitor.Cli/Commands/CursorImportSource.cs

CursorSubagentCorrelator.csAdd transcript-only subagent correlator using prompt SHA-256 hashes +122/-0

Add transcript-only subagent correlator using prompt SHA-256 hashes

• Introduces a pure correlator that scans JSONL transcripts to hash the child’s first user prompt (wrapper-stripped) and match it against parent Task/Agent tool_use prompts. Produces a childSessionId → (parentSessionId, subagentType) map with self-link protection and first-writer-wins collision behavior.

src/Capacitor.Cli/Commands/CursorSubagentCorrelator.cs

Bug fix (1) +19 / -0
CursorHookCommand.csEnrich Cursor sessionStart with repository info from workspace_roots +19/-0

Enrich Cursor sessionStart with repository info from workspace_roots

• Adds a sessionStart-only enrichment step that derives the working directory from workspace_roots[0] and injects a repository node using RepositoryDetection. The enrichment is bounded by remaining hook budget and fails open to preserve Cursor’s non-crashing contract.

src/Capacitor.Cli/Commands/CursorHookCommand.cs

Refactor (1) +50 / -17
RepositoryDetection.csExtract repository node builder and add explicit-cwd enrichment helper +50/-17

Extract repository node builder and add explicit-cwd enrichment helper

• Refactors repository JSON construction into BuildRepositoryNode for reuse and adds EnrichWithRepositoryInfoFromCwd for callers that supply a cwd instead of relying on a payload 'cwd' field. The new helper always attaches when detected (no last-emitted dedup) and fails open on errors.

src/Capacitor.Cli/RepositoryDetection.cs

Tests (2) +253 / -0
CursorImportSourceTests.csTest Cursor import repo attribution and subagent routing behavior +158/-0

Test Cursor import repo attribution and subagent routing behavior

• Adds coverage ensuring repository metadata is included/omitted on synthetic sessionStart depending on repo detection results. Adds an end-to-end import test verifying correlated subagent children are ingested under the parent via subagent hooks and transcript agent_id, without emitting child session-start/-end.

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

CursorSubagentCorrelatorTests.csAdd unit tests for prompt-hash subagent correlation +95/-0

Add unit tests for prompt-hash subagent correlation

• Validates that a child transcript is linked to the correct parent when prompts match, that unmatched prompts yield no link, and that a session is never self-linked even if it tasks with its own first prompt.

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

@qodo-code-review

qodo-code-review Bot commented Jul 2, 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


Action required

1. Subagent stop payload incomplete ✓ Resolved 🐞 Bug ≡ Correctness
Description
CursorImportSource.ImportSubagentAsync emits /hooks/subagent-start|stop payloads that omit fields
used by the established vendor-agnostic subagent hook shape (notably cwd and several stop fields),
so Cursor subagent lifecycle ingestion may be rejected or recorded incompletely. This can prevent
proper AgentSubsession nesting for Cursor child sessions.
Code

src/Capacitor.Cli/Commands/CursorImportSource.cs[R426-454]

+        var startOk = await PostSyntheticHookAsync(ctx.HttpClient, ctx.BaseUrl, "subagent-start", new JsonObject {
+            ["hook_event_name"] = "subagent_start",
+            ["session_id"]      = parentSessionId,
+            ["agent_id"]        = agentId,
+            ["agent_type"]      = subagentType,
+            ["transcript_path"] = transcriptPath,
+        }, ct);
+        if (!startOk) return ImportOutcome.Failed;
+
+        int sent;
+        try {
+            sent = await SessionImporter.SendTranscriptBatches(
+                httpClient: ctx.HttpClient,
+                baseUrl:    ctx.BaseUrl,
+                sessionId:  parentSessionId,
+                filePath:   transcriptPath,
+                agentId:    agentId,
+                startLine:  0,
+                vendor:     Vendor);
+        } catch {
+            return ImportOutcome.Failed;
+        }
+
+        var stopOk = await PostSyntheticHookAsync(ctx.HttpClient, ctx.BaseUrl, "subagent-stop", new JsonObject {
+            ["hook_event_name"] = "subagent_stop",
+            ["session_id"]      = parentSessionId,
+            ["agent_id"]        = agentId,
+            ["agent_type"]      = subagentType,
+        }, ct);
Evidence
Cursor’s new subagent hook payloads omit fields that other implementations in this repo always send;
Gemini’s implementation explicitly notes the server requires a non-null cwd, and both Gemini and
SessionImporter include the additional stop fields that Cursor omits.

src/Capacitor.Cli/Commands/CursorImportSource.cs[410-455]
src/Capacitor.Cli.Core/Gemini/GeminiSubagentDiscovery.cs[81-107]
src/Capacitor.Cli/Commands/SessionImporter.cs[412-444]

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

### Issue description
`ImportSubagentAsync` posts minimal JSON for `/hooks/subagent-start` and `/hooks/subagent-stop`. Other subagent senders in this repo include `cwd` (even if empty) and the full stop payload (`transcript_path`, `stop_hook_active`, `agent_transcript_path`, `last_assistant_message`), and at least one implementation documents that the server requires a non-null `cwd`.

### Issue Context
This Cursor subagent import path should mirror the existing vendor-agnostic wire contract used by Gemini/OpenCode and by `SessionImporter.SendAgentLifecycle`, so the server can reliably parse and emit `SubagentStarted`/`SubagentCompleted` lifecycle events.

### Fix Focus Areas
- src/Capacitor.Cli/Commands/CursorImportSource.cs[410-458]
- src/Capacitor.Cli.Core/Gemini/GeminiSubagentDiscovery.cs[81-107]
- src/Capacitor.Cli/Commands/SessionImporter.cs[412-444]

### What to change
- Add `cwd = ""` to the Cursor `/hooks/subagent-start` payload.
- For `/hooks/subagent-stop`, include at minimum:
 - `transcript_path` (use the subagent transcript path, consistent with Gemini/OpenCode)
 - `cwd = ""`
 - `stop_hook_active = false`
 - `agent_transcript_path = transcriptPath`
 - `last_assistant_message = ""`
- Consider extracting a shared helper for building start/stop payloads to avoid divergence across vendors.

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



Remediation recommended

2. Correlator can abort classify ✓ Resolved 🐞 Bug ☼ Reliability
Description
CursorSubagentCorrelator scans transcripts via File.ReadLines without guarding IO exceptions, and
CursorImportSource.ClassifyAsync calls Correlate outside the existing per-session probe try/catch. A
single unreadable/locked transcript can therefore throw and abort classification for all discovered
sessions.
Code

src/Capacitor.Cli/Commands/CursorSubagentCorrelator.cs[R64-73]

+    static string? ScanFirstUserHash(string path, out List<(string Hash, string? SubType)> tasks) {
+        tasks = [];
+        string? firstUserHash = null;
+
+        foreach (var line in File.ReadLines(path)) {
+            if (string.IsNullOrWhiteSpace(line)) continue;
+
+            JsonDocument doc;
+            try { doc = JsonDocument.Parse(line); } catch { continue; }
+
Evidence
The correlator directly enumerates File.ReadLines(path) without an enclosing try/catch, and
ClassifyAsync invokes correlation before any of its transcript-probe error handling; an exception
here would bypass the later per-session safeguards.

src/Capacitor.Cli/Commands/CursorSubagentCorrelator.cs[31-39]
src/Capacitor.Cli/Commands/CursorSubagentCorrelator.cs[64-73]
src/Capacitor.Cli/Commands/CursorImportSource.cs[186-208]

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

### Issue description
`CursorSubagentCorrelator` can throw due to unhandled IO exceptions (e.g., `UnauthorizedAccessException`, `IOException`) while enumerating transcript files. Because `CursorImportSource.ClassifyAsync` calls `Correlate(...)` before its per-session error handling, one bad file can break the entire classify/import run.

### Issue Context
The import pipeline already treats transcript probing as best-effort per session; the correlator should follow the same robustness model and never throw.

### Fix Focus Areas
- src/Capacitor.Cli/Commands/CursorSubagentCorrelator.cs[31-110]
- src/Capacitor.Cli/Commands/CursorImportSource.cs[186-208]

### What to change
- Wrap per-file scanning in `Correlate` with try/catch and skip unreadable files.
- Alternatively (or additionally), wrap the `File.ReadLines(path)` enumeration inside `ScanFirstUserHash` with try/catch and return `(firstUserHash: null, tasks: [])` on error.
- Ensure `Correlate` never throws (return an empty links map on unexpected errors).

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


3. workspace_roots string cast throws ✓ Resolved 🐞 Bug ☼ Reliability
Description
CursorHookCommand calls roots[0]?.GetValue<string>(), which throws if the JSON value is not a
string, and the outer catch then drops the entire sessionStart hook. This loses lifecycle delivery
for that invocation (and therefore repo attribution).
Code

src/Capacitor.Cli/Commands/CursorHookCommand.cs[R119-122]

+            if (eventName == "sessionStart") {
+                var workspaceRoot = node["workspace_roots"] is JsonArray roots && roots.Count > 0
+                    ? roots[0]?.GetValue<string>()
+                    : null;
Evidence
The new sessionStart enrichment block can throw during workspace_roots parsing, and HandleCore’s
outer catch returns 0 (dropping the event) on any exception.

src/Capacitor.Cli/Commands/CursorHookCommand.cs[114-131]
src/Capacitor.Cli/Commands/CursorHookCommand.cs[205-211]

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

### Issue description
`GetValue<string>()` on `JsonNode` throws when the node isn't a string-valued JSON node. In the Cursor hook dispatcher this exception is caught by the outer `try/catch`, causing the hook to return 0 early and skip POST/spooling.

### Issue Context
The intent of AI-1152 is fail-open repo enrichment. Parsing errors should result in “no repository node attached”, not “drop the event”.

### Fix Focus Areas
- src/Capacitor.Cli/Commands/CursorHookCommand.cs[114-131]
- src/Capacitor.Cli/Commands/CursorHookCommand.cs[205-211]

### What to change
- Replace `roots[0]?.GetValue<string>()` with a non-throwing extraction pattern, e.g.:
 - `roots[0] is JsonValue v && v.TryGetValue<string>(out var ws) ? ws : null`
 - or `roots[0]?.ToString()` if appropriate and safe.
- Wrap only the enrichment block in its own try/catch so any enrichment failure still posts the original event body.

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



Informational

4. Duplicate prompts misattribute parent ✓ Resolved 🐞 Bug ≡ Correctness
Description
CursorSubagentCorrelator uses a first-writer-wins map from prompt-hash to parent session, so if
multiple parents issue the same Task/Agent prompt, a child can be linked under the wrong parent.
That creates incorrect subagent nesting in the imported model.
Code

src/Capacitor.Cli/Commands/CursorSubagentCorrelator.cs[R41-46]

+        // promptHash → (parentSessionId, subagentType). First writer wins on the (rare)
+        // case of two parents Tasking an identical prompt.
+        var parentByPrompt = new Dictionary<string, (string Parent, string? SubType)>(StringComparer.Ordinal);
+        foreach (var s in scanned)
+            foreach (var (hash, subType) in s.Tasks)
+                parentByPrompt.TryAdd(hash, (s.Sid, subType));
Evidence
The correlator explicitly documents and implements “first writer wins” for identical prompts using
TryAdd, which necessarily misattributes children in ambiguous cases.

src/Capacitor.Cli/Commands/CursorSubagentCorrelator.cs[41-46]

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

### Issue description
When two (or more) parent sessions issue identical Task/Agent prompts, the correlator currently picks the first parent seen (`TryAdd`) and links any matching child to that parent. This can produce incorrect parent/child attribution.

### Issue Context
The data is inherently ambiguous in this scenario, so the safest behavior is usually to avoid linking rather than link incorrectly.

### Fix Focus Areas
- src/Capacitor.Cli/Commands/CursorSubagentCorrelator.cs[41-55]

### What to change
- Track prompt-hash collisions (e.g., store a list of parents per hash).
- If a hash maps to multiple distinct parents, treat it as ambiguous and do not emit any link for that hash (or require an additional disambiguator if available).
- Optionally log/trace ambiguity counts for debugging (if there is an existing logging mechanism for import).

ⓘ 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/CursorImportSource.cs
Comment thread src/Capacitor.Cli/Commands/CursorSubagentCorrelator.cs
Comment thread src/Capacitor.Cli/Commands/CursorSubagentCorrelator.cs Outdated
Comment thread src/Capacitor.Cli/Commands/CursorHookCommand.cs Outdated
Comment thread src/Capacitor.Cli/Commands/CursorImportSource.cs
Comment thread src/Capacitor.Cli/Commands/CursorImportSource.cs Outdated
Comment thread src/Capacitor.Cli/Commands/CursorImportSource.cs
Comment thread src/Capacitor.Cli/Commands/CursorSubagentCorrelator.cs
Comment thread src/Capacitor.Cli/Commands/CursorImportSource.cs
Comment thread src/Capacitor.Cli/Commands/CursorImportSource.cs
Comment thread src/Capacitor.Cli/Commands/CursorImportSource.cs
Comment thread src/Capacitor.Cli/Commands/CursorImportSource.cs
@realtonyyoung

Copy link
Copy Markdown
Collaborator Author

[P1] This appears to revert the AI-30 bracketed-paste path back to raw text plus Enter, and the dedicated regression test is deleted in the same branch comparison. The previous code intentionally wrapped server-origin input in ESC[200~/ESC[201~ so large or multiline messages are consumed as one paste before the separate CR. With raw WriteAsync(message), Codex/Claude can fold the CR into the still-ingesting paste/newline sequence and leave the prompt sitting in the composer instead of submitting. Please keep the bracketed-paste write (and its test) unless this PR adds an equivalent submit guarantee.

Note: I am posting this as a top-level comment because GitHub would not accept it inline; the file was not present in the authoritative gh pr diff patch even though it appeared in the local branch comparison.

realtonyyoung and others added 5 commits July 2, 2026 21:17
…rent

Repo attribution (AI-1152): detect the git repo CLI-side and attach a
`repository` node to the Cursor sessionStart so the server emits
RepositoryDetected and the session groups under its repo. Cursor payloads
carry `workspace_roots` (not `cwd`), so add
RepositoryDetection.EnrichWithRepositoryInfoFromCwd + extract BuildRepositoryNode;
the live hook enriches from workspace_roots[0] and the import path attaches
the detected repo.

Subagent nesting (AI-1153): a Cursor subagent runs as its own session with no
explicit parent link — only the child's first user_query matches the parent's
Task/Agent prompt. New CursorSubagentCorrelator recovers that link by prompt
hash; CursorImportSource then ingests a detected child under the parent's
AgentSubsession stream (subagent-start + transcript-with-agent_id +
subagent-stop) instead of as a standalone top-level session. Also fixes the
subagent half of backfill (AI-1154) on `kcap import cursor`.

Pairs with the kcap-server PR (shared repo recorder + Cursor normalizer id
scoping for subsessions).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ing + resume)

Round-1 review findings on the subagent import path:

1. Ordering — children were imported as separate routed sessions in parallel with
   the parent; a parent's session-end could land before a child's subagent-start,
   and SubagentStarted (a reactivation event) would flip the ended parent back to
   Active. Fix: the parent now imports its subagent children inline, BEFORE its own
   session-end (mirrors SessionImporter.SendAgentLifecycle), so the parent's
   SessionEnded stays the last event on its stream. Children are marked
   IsSubagentChild and their own routed import no-ops (Skipped).

2. Watermark — the child was probed against its own (now non-existent) top-level
   stream, so re-imports reposted the full child transcript from line 0. Fix:
   SendSubagentLifecycleAsync resumes from the subsession watermark
   (GET /api/sessions/{parent}/last-line?agentId={child}); FetchServerLastLineAsync
   gained an optional agentId.

Tests: parent_import_nests_subagent_child_before_its_own_session_end (ordering guard),
subagent_child_import_is_a_noop_handled_by_the_parent. CursorImportSource suite 35/35.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Subagent-stop payload now sends the full SubagentStopHook shape
  (stop_hook_active, agent_transcript_path, last_assistant_message) — an
  incomplete body can be rejected before HandleSubagentStop. [P1]
- Child transcript send now uses failOnError:true so a rejected/failed POST
  aborts the parent import (fail-closed, re-run repairs) instead of leaving an
  empty completed subagent while reporting success. [P1]
- CursorSubagentCorrelator orders sessions by id before building the
  prompt->parent map, so "first writer wins" is deterministic across runs
  (filesystem discovery order is not stable). [P2]
- Correlation no longer aborts classification: a single unreadable/malformed
  transcript is skipped (per-session try/catch) instead of throwing out of
  ClassifyAsync.
- CursorHookCommand extracts workspace_roots[0] safely (JsonValue.TryGetValue)
  so a non-string element can't throw and drop the whole sessionStart hook.

Tests: CursorImportSource 36/36 (adds fail-closed + full-stop-payload guards),
CursorSubagentCorrelator 5/5 (adds determinism + bad-entry-skip). AOT clean.

Known limitation (filed as follow-up): correlation only sees the discovered
(filtered) session slice, so a --session/--cwd/--since import that excludes the
parent won't nest the child. Full import (and backfill) correlate correctly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Duplicate Task prompts across DISTINCT parents are now treated as ambiguous:
  the correlator drops the link instead of attributing the child to a guessed
  parent (a single parent Tasking the same prompt more than once is still fine).
  Supersedes the earlier order-by-id "first writer wins".
- Subagent-start/-stop payloads now include cwd (and stop adds transcript_path)
  for wire-shape parity with SendAgentLifecycle / the other vendors' subagent
  senders (defensive — cwd isn't required by the current SubagentStart/StopHook
  binding, but matches the established contract).

Tests: CursorSubagentCorrelator 6/6 (ambiguous-no-link + same-parent-twice),
CursorImportSource 36/36. AOT clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ail-closed subagent import

HookBase declares `cwd` and `transcript_path` as REQUIRED, so the earlier subagent
start/stop payloads (missing them) would have been rejected by the server (400) →
parent import fail. The full shape is now sent on both hooks (cwd + transcript_path;
added in the previous commit for start/stop, verified complete here).

Completes the fail-closed contract so a re-run reliably repairs a partial import:
- subagent-start / subagent-stop now send `strict: true`; the server's
  HandleSubagentStart/Stop return 500 (not 200) when the SubagentStarted/Completed
  write fails under strict, so PostSyntheticHookAsync sees the failure and the parent
  import aborts before session-end.
- CLI TranscriptBatch gains a `strict` field, and PostTranscriptBatch sets
  Strict = failOnError. Previously failOnError only threw on non-2xx HTTP/transport
  errors; the server only turns per-line normalization failures into a non-2xx
  response when Strict, so a failed child line could return 200 and the import would
  proceed. Now fail-closed callers (the Cursor subagent transcript; OpenCode import)
  also fail on server-side normalization failures.

Tests: full CLI unit suite 2079/2079 (adds cwd/transcript_path/strict assertions on
both subagent hooks + strict on the subagent transcript). AOT clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@realtonyyoung
realtonyyoung force-pushed the cursor-repo-attribution-and-subagents branch from 4ff9967 to f1a9e5d Compare July 3, 2026 01:18
@realtonyyoung

Copy link
Copy Markdown
Collaborator Author

This is a stale-base diff artifact, not a real change — the PR does not touch the bracketed-paste path. Root cause: this branch was cut from the pinned submodule commit (f72828c), which was 3 commits behind kcap-cli main — including AI-30 (#237). So a two-dot git diff main..branch showed AI-30's AgentOrchestrator bracketed-paste code (and its test) as "removed on the branch", even though the branch never modified those files (which is why it wasn't in the authoritative gh pr diff). On squash-merge the 3-way merge keeps main's version — no revert.

Fixed regardless: I rebased the branch onto current kcap-cli origin/main (now 0 behind). The bracketed-paste path in AgentOrchestrator.cs and AgentOrchestratorBracketedPasteTests.cs are present and untouched; the PR's only changes remain the 8 Cursor files. CLI suite green, AOT clean.

🤖 Addressed by Claude Code

@alexeyzimarev
alexeyzimarev merged commit 1d3f85a into main Jul 3, 2026
5 checks passed
@alexeyzimarev
alexeyzimarev deleted the cursor-repo-attribution-and-subagents branch July 3, 2026 07:18
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.

2 participants