Skip to content

AI-1458: add shared SessionStart memory foundation - #350

Merged
realtonyyoung merged 3 commits into
mainfrom
codex/ai-1458-sessionstart-memory-foundation
Jul 23, 2026
Merged

realtonyyoung merged 3 commits into
mainfrom
codex/ai-1458-sessionstart-memory-foundation

Conversation

@realtonyyoung

@realtonyyoung realtonyyoung commented Jul 23, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • add a typed, source-generated memory-index provider and exact output adapters for all supported harnesses
  • add lifecycle identity/policy, fenced cross-process leases, retry cooldowns, bounded persistent state, cleanup cursors, and extension delivery state
  • migrate Claude to the shared provider while preserving its existing context composition and fail-open hook behavior
  • document foundation vs activation/live-certification status

Companion server contract PR: https://github.com/kurrent-io/kcap-server/pull/1169
Linear: https://linear.app/kurrent/issue/AI-1458/sessionstart-memory-index-shared-provider-lifecycle-marker-and

Verification

  • CLI unit project builds cleanly
  • SessionStartMemoryFoundationTests: 17/17
  • MemoryIndexEmitterTests: 12/12
  • MemoryIndexUrlTests: 4/4
  • ClaudeHookCommandTests: 24/24
  • full suite: 3,717 passed; 42 unrelated existing Codex TOML temp-path failures; 4 skipped
  • local NativeAOT compile reaches ILLink, then the installed SDK fails to launch its arm64 MSBuild task host (MSB4216); PR CI remains the acceptance gate

Review history

  • spec review flow 8ebc699d040b414aa1ada75bd0b467aa approved round 5

@linear-code

linear-code Bot commented Jul 23, 2026

Copy link
Copy Markdown

AI-1458

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add shared SessionStart memory foundation with leases, adapters, and Claude wiring

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Introduce a shared, typed SessionStart memory fetch/render foundation with bounded output.
• Add cross-process lifecycle identity, fenced leases, retry cooldowns, and store cleanup cursors.
• Migrate Claude hook to the shared provider while preserving fail-open behavior and budgets.
Diagram

graph TD
  A["ClaudeHookCommand"] --> B["Memory Orchestrator"] --> C[("Lease Store")]
  C --> D["Context Provider"] --> E{{"KCAP server\n/api/memories/index"}} --> F["MemoryIndexEmitter"] --> G["AdditionalContext\nenvelope"]

  subgraph Legend
    direction LR
    _cmd["Command/module"] ~~~ _store[("File-backed store")] ~~~ _ext{{"External service"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Server-side idempotency key + cache
  • ➕ Eliminates local file store complexity and filesystem failure modes
  • ➕ Centralizes retry/cooldown policy in one place
  • ➖ Requires server changes and careful multi-client semantics
  • ➖ Harder to keep hook fail-open/low-latency guarantees during server incidents
2. OS-level named mutex + in-memory cache only
  • ➕ Simpler coordination than a persistent JSON store
  • ➕ Avoids on-disk record growth/cleanup logic
  • ➖ Does not persist completion across process restarts
  • ➖ Cross-platform mutex semantics and sandboxing can be inconsistent
3. Always fetch without dedupe, rely only on hook budget
  • ➕ Very simple client implementation
  • ➕ No coordination state to maintain
  • ➖ Duplicate calls across parallel hooks can amplify load and rate limits
  • ➖ Worse user experience under transient failures (repeated retries every start)

Recommendation: Keep the current approach: a local fenced lease store + lifecycle policy provides cross-process dedupe, bounded retries, and a hard fail-open posture without requiring server-side behavioral changes. The added complexity is justified by preventing thundering-herd session-start fetches and by making completion/retention guarantees explicit and testable.

Files changed (19) +1270 / -112

Enhancement (14) +864 / -71
ClaudeHookCommand.csMigrate Claude SessionStart memory index to shared orchestrator and typed provider +41/-71

Migrate Claude SessionStart memory index to shared orchestrator and typed provider

• Replaces bespoke memory-index fetch logic with SessionStartMemoryOrchestrator + ContextProvider under lifecycle policy and leased coordination. Adds support for parsing native session_id, maps lifecycle reasons, and ensures memory fetch uses a no-redirect HttpClient while preserving fail-open behavior.

src/Capacitor.Cli/Commands/ClaudeHookCommand.cs

BoundedJsonFile.csAdd bounded JSON read and atomic write utilities for store records +49/-0

Add bounded JSON read and atomic write utilities for store records

• Implements strict UTF-8 validation, byte-limited reads, and disallows trailing data/comments/commas. Adds atomic temp-file writes with best-effort owner-only permissions on Unix.

src/Capacitor.Cli/SessionStartMemory/BoundedJsonFile.cs

PiSessionPathCanonicalizer.csCanonicalize and hash Pi session paths for stable identity +22/-0

Canonicalize and hash Pi session paths for stable identity

• Normalizes rooted paths (separator normalization, NFC, case normalization on Windows) and hashes them with SHA-256. Prevents unsafe inputs (NUL, non-rooted) from participating in identity generation.

src/Capacitor.Cli/SessionStartMemory/PiSessionPathCanonicalizer.cs

SessionStartMemoryContextProvider.csAdd typed memory-index fetcher with scope resolution, retries, and bounded response +95/-0

Add typed memory-index fetcher with scope resolution, retries, and bounded response

• Resolves repo/machine scope, fetches /api/memories/index with header-only completion and a response size cap, and maps status codes to Ready/Empty/Retry outcomes. Implements one-time refresh on 401 and parses Retry-After for 429.

src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryContextProvider.cs

SessionStartMemoryContracts.csDefine harness/lifecycle contracts and store record schemas +93/-0

Define harness/lifecycle contracts and store record schemas

• Adds enums and records for harness identity, lifecycle classification, typed memory entries, provider results, and store record/metadata schemas. Centralizes constants for limits, retention, lease duration, and versioning.

src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryContracts.cs

SessionStartMemoryExtensionState.csTrack extension bridge memory fragments for one-time delivery +31/-0

Track extension bridge memory fragments for one-time delivery

• Adds a concurrency-safe in-memory state that records the first non-empty fragment observed per key and allows it to be delivered exactly once.

src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryExtensionState.cs

SessionStartMemoryIdentity.csAdd stable session identity normalization and key hashing +50/-0

Add stable session identity normalization and key hashing

• Normalizes session IDs by harness (UUID normalization, Pi path hashing) and generates a length-delimited, lifecycle-scoped SHA-256 key. Prevents identity ambiguity between concatenated fields and distinguishes absent lifecycle instance IDs.

src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryIdentity.cs

SessionStartMemoryJsonContext.csAdd source-generated JSON metadata context and output envelope shapes +39/-0

Add source-generated JSON metadata context and output envelope shapes

• Introduces System.Text.Json source generation metadata for typed entries and store schemas. Defines exact envelope record types for each supported harness output shape to ensure golden output bytes.

src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryJsonContext.cs

SessionStartMemoryLeaseStore.csImplement fenced, cross-process lease store with retention sweeps and cooldowns +322/-0

Implement fenced, cross-process lease store with retention sweeps and cooldowns

• Adds a local file-backed store with an exclusive lock, generation+token fencing, retry backoff with cooldown scheduling, capacity enforcement, and retention-based sweeping using a persisted cursor. Protects against unsafe roots (symlinks/reparse points) and uses bounded atomic JSON IO.

src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryLeaseStore.cs

SessionStartMemoryLifecyclePolicy.csAdd lifecycle eligibility policy for memory injection +15/-0

Add lifecycle eligibility policy for memory injection

• Encodes rules for when memory injection is eligible, ineligible, or should retry without committing state. Guards against non-authoritative lifecycle classification, unknown reasons, compact callbacks, and sub-agent callbacks.

src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryLifecyclePolicy.cs

SessionStartMemoryOrchestrator.csAdd orchestrator combining lifecycle policy, lease coordination, and provider results +31/-0

Add orchestrator combining lifecycle policy, lease coordination, and provider results

• Coordinates the end-to-end decision: check policy, acquire lease, fetch memory index, and commit completion or retry scheduling. Returns fragments only to the commit winner and stays fail-open on non-fatal exceptions.

src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryOrchestrator.cs

SessionStartMemoryOutputAdapters.csAdd per-harness memory output adapters with exact JSON shapes +36/-0

Add per-harness memory output adapters with exact JSON shapes

• Renders the memory fragment into the correct envelope per harness (Claude/Codex/Cursor/Copilot/Gemini/Antigravity) and supports plain-text append modes for Kiro/Pi/OpenCode. Ensures newline/empty-output semantics match harness expectations.

src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryOutputAdapters.cs

SessionStartMemoryScopeResolver.csResolve repo hash and machine tag for memory index scoping +19/-0

Resolve repo hash and machine tag for memory index scoping

• Computes repo hash from detected repo owner/name and reads a machine identifier, tolerating missing contexts. Returns a scope object used to shape server query parameters.

src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryScopeResolver.cs

SessionStartMemoryStorePaths.csAdd validated default store root with safe permissions +21/-0

Add validated default store root with safe permissions

• Defines the default cache location under config paths and enforces a non-symlink/reparse-point root. Applies owner-only directory permissions on Unix where possible.

src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryStorePaths.cs

Bug fix (1) +3 / -2
HttpClientExtensions.csAllow disabling auto-redirects when creating authenticated clients +3/-2

Allow disabling auto-redirects when creating authenticated clients

• Extends CreateClientWithAuthStatusAsync to accept an allowAutoRedirect flag and uses HttpClientHandler accordingly. Enables callers to refuse redirects (important for strict endpoint contract behavior).

src/Capacitor.Cli.Core/HttpClientExtensions.cs

Refactor (1) +71 / -38
MemoryIndexEmitter.csAdd typed memory-index fragment renderer with strict normalization and size bounds +71/-38

Add typed memory-index fragment renderer with strict normalization and size bounds

• Introduces a typed BuildFragment(SessionStartMemoryEntry[]) path that validates kinds, normalizes Unicode/whitespace, caps entries, and enforces a max fragment byte budget with early truncation. Keeps legacy JsonNode adapter by converting to typed entries before rendering.

src/Capacitor.Cli/MemoryIndexEmitter.cs

Tests (2) +315 / -1
ClaudeHookCommandTests.csInject deterministic SessionStartMemory lease store into Claude hook tests +3/-1

Inject deterministic SessionStartMemory lease store into Claude hook tests

• Updates the Claude hook test fixture to supply a dedicated SessionStartMemoryLeaseStore rooted in the test temp home. Improves isolation and avoids interference across tests.

test/Capacitor.Cli.Tests.Unit/ClaudeHookCommandTests.cs

SessionStartMemoryFoundationTests.csAdd comprehensive tests for identity, policy, leases, provider behavior, and adapters +312/-0

Add comprehensive tests for identity, policy, leases, provider behavior, and adapters

• Adds a new test suite validating key derivation, lifecycle policy decisions, lease fencing and cooldowns, sweep behavior and cursor progression, provider HTTP/status mappings (including redirect refusal and 401 refresh), and golden output bytes for all harness adapters.

test/Capacitor.Cli.Tests.Unit/SessionStartMemory/SessionStartMemoryFoundationTests.cs

Documentation (1) +17 / -0
README.mdDocument shared SessionStart memory foundation vs harness activation status +17/-0

Document shared SessionStart memory foundation vs harness activation status

• Adds a table clarifying which harnesses have the shared foundation implemented versus actually wired/activated. Explains that only Claude is connected in this release and others will be activated via follow-up issues.

README.md

@qodo-code-review

qodo-code-review Bot commented Jul 23, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
⚠️ Tickets: not configured — ticket URL found in PR but could not be fetched — check ticket provider credentials

Grey Divider


Remediation recommended

1. Opt-out still writes leases ✓ Resolved 🐞 Bug ≡ Correctness
Description
ClaudeHookCommand runs SessionStartMemoryOrchestrator even when disable_memory_index is true, so
opting out still acquires a lease and writes a completed record. This creates unnecessary disk I/O
and can suppress memory injection if the user later re-enables the feature for the same session
identity until the retention sweep clears the record.
Code

src/Capacitor.Cli/Commands/ClaudeHookCommand.cs[R509-530]

+            var memoryDisabled = AppConfig.ResolvedProfile?.Profile?.DisableMemoryIndex is true;
+            var lifecycleReason = source?.ToLowerInvariant() switch {
+                "resume" => SessionLifecycleReason.Resume,
+                "reopen" => SessionLifecycleReason.Reopen,
+                "fork" => SessionLifecycleReason.Fork,
+                "compact" => SessionLifecycleReason.Compact,
+                _ => SessionLifecycleReason.New
+            };
+            Task<string?> memoryIndexTask = string.IsNullOrEmpty(nativeSessionId)
+                ? Task.FromResult<string?>(null)
+                : new SessionStartMemoryOrchestrator(
+                        memoryStoreFactory?.Invoke() ?? new SessionStartMemoryLeaseStore(),
+                        new SessionStartMemoryContextProvider(
+                            new SessionStartMemoryScopeResolver(),
+                            memoryClientFactory ?? (_ => Task.FromResult(client)),
+                            disposeClients: memoryClientFactory is not null))
+                    .GetFragmentAsync(
+                        new SessionMemoryLifecycle(SessionStartHarness.Claude, nativeSessionId, null,
+                            IsTopLevel: true, ClassificationAuthoritative: true, lifecycleReason,
+                            CallbackMayRepeat: false),
+                        new SessionStartMemoryContextRequest(baseUrl, sessionCwd, memoryDisabled,
+                            HookBudget.Remaining(processStart, "session-start"), CancellationToken.None));
Evidence
The hook passes memoryDisabled only into the request, but still invokes the orchestrator. The
provider immediately returns Empty when disabled; however the orchestrator takes a lease and then
commits completion for any non-retry disposition, and the lease store blocks future begins for
completed records.

src/Capacitor.Cli/Commands/ClaudeHookCommand.cs[504-531]
src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryContextProvider.cs[12-15]
src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryOrchestrator.cs[11-25]
src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryLeaseStore.cs[27-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
`disable_memory_index` should behave as a true no-op: no network work and **no lease-store coordination writes**. Currently the request is marked `Disabled`, but orchestration still takes a lease and commits a completed record.

## Issue Context
- `SessionStartMemoryContextProvider.GetAsync()` returns `Empty` immediately when `request.Disabled` is true.
- `SessionStartMemoryOrchestrator` acquires the lease **before** calling the provider, and commits `CompleteWithoutContext`, which the store then treats as authoritative (future `TryBeginAsync` returns null until swept).

## Fix Focus Areas
- src/Capacitor.Cli/Commands/ClaudeHookCommand.cs[509-530]
- src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryOrchestrator.cs[11-25]
- src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryContextProvider.cs[12-15]

### Suggested change
In `ClaudeHookCommand`, set `memoryIndexTask` to `Task.FromResult<string?>(null)` when `memoryDisabled` is true (and/or when budget is already exhausted), so orchestration is never invoked.

Optionally (defense-in-depth): also add an early return in `SessionStartMemoryOrchestrator.GetFragmentAsync` when `request.Disabled` is true, before leasing.

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


2. Filtered entries cause retries ✓ Resolved 🐞 Bug ☼ Reliability
Description
SessionStartMemoryContextProvider maps BuildFragment(entries) == null to a retryable failure even
when the HTTP response is 2xx and JSON-deserialization succeeded. When all entries are filtered out
(e.g., unsupported kinds/missing fields), this needlessly persists retry_pending state and can
cause repeated fetch attempts instead of completing without context.
Code

src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryContextProvider.cs[R39-47]

+                var bytes = await ReadBoundedAsync(response.Content, cts.Token);
+                var entries = JsonSerializer.Deserialize(bytes,
+                    SessionStartMemoryJsonContext.Default.SessionStartMemoryEntryArray);
+                if (entries is null) return SessionStartMemoryContextResult.Retry;
+                if (entries.Length == 0) return SessionStartMemoryContextResult.Empty;
+                var fragment = MemoryIndexEmitter.BuildFragment(entries);
+                return fragment is null
+                    ? SessionStartMemoryContextResult.Retry
+                    : new SessionStartMemoryContextResult(SessionStartMemoryDisposition.Ready, fragment);
Evidence
Provider returns Retry when fragment is null, and the emitter returns null when it filters out all
entries. The orchestrator converts retryable failures into retry_pending store state, creating
coordination churn even for successful-but-unrenderable responses.

src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryContextProvider.cs[39-47]
src/Capacitor.Cli/MemoryIndexEmitter.cs[29-49]
src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryOrchestrator.cs[19-22]

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

## Issue description
A syntactically valid, successful response that yields no renderable entries should be treated as `CompleteWithoutContext`, not `RetryableFailure`. Returning `Retry` causes lease-store retry scheduling and repeat work without a clear transient failure.

## Issue Context
- `MemoryIndexEmitter.BuildFragment(...)` returns null when it accepts zero entries.
- `SessionStartMemoryContextProvider` currently returns `Retry` in that case.
- The orchestrator persists retry state via `store.RetryAsync(...)`.

## Fix Focus Areas
- src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryContextProvider.cs[39-47]
- src/Capacitor.Cli/MemoryIndexEmitter.cs[29-57]
- src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryOrchestrator.cs[19-22]

### Suggested change
Change the mapping so that `fragment is null` returns `SessionStartMemoryContextResult.Empty` (or a new distinct disposition if you want to preserve diagnostics), while keeping truly malformed JSON/transport failures as retryable.

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


3. Claude ID not canonicalized ✓ Resolved 🐞 Bug ☼ Reliability
Description
SessionStartMemoryIdentity does not normalize Claude session IDs, while ClaudeHookCommand elsewhere
strips dashes from session_id for watcher/spool/server payloads; as a result, logically equivalent
IDs with/without dashes can map to different lease keys. This can duplicate lease records and defeat
cross-process deduplication, causing redundant memory fetches.
Code

src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryIdentity.cs[R20-27]

+    public static string? NormalizeSessionId(SessionStartHarness harness, string? value) {
+        if (string.IsNullOrEmpty(value)) return null;
+        if (harness is SessionStartHarness.Cursor or SessionStartHarness.Copilot or SessionStartHarness.Antigravity)
+            return Guid.TryParse(value, out var guid) ? guid.ToString("N") : null;
+        if (harness == SessionStartHarness.Pi)
+            return PiSessionPathCanonicalizer.TryHash(value, out var hash) ? hash : null;
+        return value;
+    }
Evidence
ClaudeHookCommand treats session_id as dash-containing and strips dashes in one path, while lease
identity normalization for Claude returns the original value unchanged, enabling dashed/dashless
variants to produce distinct hashed keys.

src/Capacitor.Cli/Commands/ClaudeHookCommand.cs[47-58]
src/Capacitor.Cli/Commands/ClaudeHookCommand.cs[178-187]
src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryIdentity.cs[20-27]

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

## Issue description
Lease identity should be based on a canonical session ID representation. Today, Claude session IDs are left “as-is” in `SessionStartMemoryIdentity.NormalizeSessionId`, even though other Claude paths treat the same field as GUID-like and strip `-`.

## Issue Context
- `ClaudeHookCommand` strips dashes from `session_id` during its early parse.
- `SessionStartMemoryIdentity.NormalizeSessionId` only normalizes Cursor/Copilot/Antigravity GUIDs (and Pi paths), not Claude.

## Fix Focus Areas
- src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryIdentity.cs[20-27]
- src/Capacitor.Cli/Commands/ClaudeHookCommand.cs[47-58]
- src/Capacitor.Cli/Commands/ClaudeHookCommand.cs[504-530]

### Suggested change
Either:
1) Add Claude to the GUID-normalized harness list in `NormalizeSessionId` (prefer `Guid.TryParse(...).ToString("N")`), **or**
2) Canonicalize `nativeSessionId` before building `SessionMemoryLifecycle` in `ClaudeHookCommand` (e.g., remove `-`), and keep identity logic unchanged.

Also consider adding a small max-length guard in normalization to avoid hashing arbitrarily large IDs.

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



Informational

4. Quadratic byte counting ✓ Resolved 🐞 Bug ➹ Performance
Description
MemoryIndexEmitter enforces the fragment byte cap by calling sb.ToString() and re-counting UTF-8
bytes on every appended line, which is O(n²) and allocates repeatedly. This is bounded by
MaxEntries/MaxFragmentBytes but is still avoidable work on every session-start.
Code

src/Capacitor.Cli/MemoryIndexEmitter.cs[R93-100]

+    static bool AppendBoundedGroup(StringBuilder sb, string heading, List<string> lines) {
+        if (lines.Count == 0) return true;
+        var headerWritten = false;
+        foreach (var line in lines) {
+            var addition = (headerWritten ? "\n" : $"\n\n### {heading}\n") + line;
+            if (Encoding.UTF8.GetByteCount(sb.ToString()) + Encoding.UTF8.GetByteCount(addition) > SessionStartMemoryConstants.MaxFragmentBytes)
+                return false;
+            sb.Append(addition);
Evidence
AppendBoundedGroup converts the entire builder to a string and counts UTF-8 bytes inside the
per-line loop, so work grows with the already-built fragment each iteration.

src/Capacitor.Cli/MemoryIndexEmitter.cs[93-103]

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

## Issue description
Fragment-size enforcement currently recomputes the UTF-8 byte size of the entire `StringBuilder` on each loop iteration, causing repeated allocations and quadratic work.

## Issue Context
The fragment is capped (24 KiB) and entries are capped (200), so the impact is bounded, but the implementation can be made linear easily.

## Fix Focus Areas
- src/Capacitor.Cli/MemoryIndexEmitter.cs[93-103]

### Suggested change
Maintain a running `currentBytes` counter for the builder (initialize from the prefix bytes), then for each `addition` compute `Encoding.UTF8.GetByteCount(addition)` once and compare `currentBytes + additionBytes` against the cap; append and increment on success.

ⓘ 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/ClaudeHookCommand.cs Outdated
Comment thread src/Capacitor.Cli/SessionStartMemory/SessionStartMemoryIdentity.cs
Comment thread src/Capacitor.Cli/MemoryIndexEmitter.cs Outdated
@realtonyyoung
realtonyyoung merged commit 99b820e into main Jul 23, 2026
6 checks passed
@realtonyyoung
realtonyyoung deleted the codex/ai-1458-sessionstart-memory-foundation branch July 23, 2026 12:41
alexeyzimarev pushed a commit that referenced this pull request Jul 24, 2026
…-cert scaffold (#359)

* test: Claude SessionStart memory-index behavioral baseline + live-cert scaffold

The ClaudeHookCommand migration onto the shared SessionStart memory provider
already landed with the AI-1458 foundation (#350); this adds the behavioral
baseline that pins no regression, plus a gated live model-receipt cert.

- 9 characterization tests: lessons+nudge+memory fragment ordering; only-ready-memory;
  empty-array / 204 / 5xx memory-index responses emit nothing (hook never fails);
  no re-fetch on a second session-start for the same session; memory-GET timeout does
  not suppress lessons/nudge; exhausted budget never touches the provider; a ready
  memory index is discarded when the session-start POST fails.
- ClaudeMemoryIndexLiveCertTests: env-gated (KCAP_CURSOR/CLAUDE live) nonce-via-
  additionalContext model-receipt cert + disabled negative control, plus ungated
  ExtractAssistantAnswer parser coverage. No production change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test: trim verbose live-cert header comments (Qodo finding 1)

The ~30-line XML <summary> on ClaudeMemoryIndexLiveCertTests and the
multi-paragraph banner comment above the SessionStart memory-index
baseline tests in ClaudeHookCommandTests read like runbook prose.
Trim both to a short intent summary — what the file/block covers,
that the live tests are env-gated manual release gates, and the
--print-parser-unverified caveat — without touching any parser or
gating logic.

* test: fix AppConfig leak in disabled-memory-index lease-store test (Qodo finding 2)

disabled_memory_index_does_not_construct_the_lease_store mutated the
process-global AppConfig resolved state directly and restored a fresh
default Profile instead of whatever was resolved before it ran, and
had no parallel guard against interleaving with the file's other
AppConfig/Console.Out-mutating tests. Route it through the existing
WithProfileAsync helper (which captures + restores the original
ResolvedServerUrl/ResolvedProfile regardless of run order or a
mid-test exception) and add [NotInParallel], matching this file's
precedent for every other test that touches the same shared state.

* test: normalize CRLF before splitting kcap config show's JSON block (Qodo finding 4)

ReadDisableMemoryIndexAsync split stdout on "\n\n" to isolate the
leading JSON block from kcap config show's "JSON, blank line, Path:"
output shape. On Windows the blank line is "\r\n\r\n", so the split
never matched, JsonNode.Parse threw on the whole CRLF blob, and
ReadDisableMemoryIndexAsync silently returned null — which would make
the negative-control test's restore step wrongly write "false" even
when disable_memory_index was originally true.

Extract the split into ExtractLeadingJsonBlock, normalizing \r\n to \n
first, and add CI-safe coverage for both the LF and CRLF shapes.

* test: kill spawned process on timeout instead of leaking it (Qodo finding 3)

RunProcessAsync's timeout path let WaitForExitAsync/ReadToEndAsync
throw OperationCanceledException without ever signaling the spawned
child, leaving a hung `claude`/`kcap` process running past the test.
Wrap the read/wait in a finally that kills the whole process tree
whenever the process hasn't already exited, and add a CI-safe test
(spawning a long-lived cross-platform process the same way
Daemon.DummyProcess does) that confirms the child is actually gone
after a timeout, not just that the await unblocked.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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