Skip to content

Preserve image replay compatibility across CLI image limits - #2670

Merged
stephentoub merged 5 commits into
mainfrom
stephentoub-sdk-image-recordings
Sep 16, 2026
Merged

stephentoub merged 5 commits into
mainfrom
stephentoub-sdk-image-recordings

Conversation

@stephentoub

@stephentoub stephentoub commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

Purpose

Companion to the intentional temporary 50-image policy change in github/copilot-agent-runtime#20783, exposed by CAPI CI for github/copilot-agent-runtime#18323. This PR preserves strict replay compatibility across supported CLI image limits and repairs independent SDK test defects exposed while validating the change. It does not change runtime image policy, weaken replay matching, extend test deadlines, or claim to fix the unresolved native Python resume hang.

The branch is rebased onto current main (583f8f1a, CLI 1.0.85) and contains five focused commits:

  1. Preserve exact cap-50 and legacy cap-one image histories.
  2. Preserve strict subagent replay across both legal completion-notification orders.
  3. Retain failure-only native evidence for Python timeouts.
  4. Publish the Rust host-crash fixture PID atomically.
  5. Isolate Go in-process E2E environments without changing the public Go API.

Image recording compatibility

SDK #2634 replaced two authentic [image] histories with one-image-limit warnings. Cap-50 runtimes send the image again; that is the first mismatch, while later retries and proxy 500s are consequences. Current main now contains the cap-50 recordings. This PR retains those histories and adds the exact cap-one alternatives required by older supported CLI 1.0.84-8.

The affected files are:

  • test/snapshots/session_config/should_accept_blob_attachments.yaml
  • test/snapshots/session_config/vision_disabled_then_enabled_via_setmodel.yaml
  • test/harness/replayingCapiProxy.test.ts

The normalizer remains unchanged. HTTP regressions require both exact histories to reach the final assistant response with finish_reason: stop; missing images and unrecorded image-limit variants still fail. Both vision-disabled and vision-enabled assertions remain intact. session/should_accept_blob_attachments.yaml is unchanged because it has no follow-up view exchange.

Evidence used the same cap-50 Windows runtime before and after: runtime run 34949630134, job 104318144383, checkout 929c696286c2eaecf66ee8946a2377cb47a15fed, including policy merge 9a28569ea31113d48975999a4b0beb66d8af2252; app.js SHA-256 6f1ead6e3458b86204e23d2a170086688737d89e14bf1d6a0110f83cb05be048.

  • Unchanged SDK + cap-50 runtime: the two C# cases failed at the first image-versus-cap-one mismatch.
  • Final histories + the same cap-50 runtime: both passed.
  • Cap-50-only histories + CLI 1.0.84-8: both failed with the inverse mismatch, proving the legacy alternative is required.
  • Final histories: complete C# session/config classes passed 66/66 on each runtime; affected Node, Python, Go, and shared-harness coverage also passed.

Strict subagent replay

The initial PR CI exposed a legal ordering where the child-completion notification arrives before the parent's waiting response. 8331dde7 adds one finite exact alternative using the observed notification and existing assistant/tool/final-answer content. It does not add a hook barrier, production ordering change, permissive matcher, or normalization rule.

The Rust hook test still requires the final subagent answer followed by session.idle, preserving hook, session-ID, and request-metadata assertions. Seven HTTP regressions cover both legal orders with streaming on and off plus malformed-history rejection. The shared harness suite passes 43/43 on the rebased head.

Python timeout evidence

54f91678 preserves failure-only .pytest-diagnostics artifacts for both Python CI matrices and discovers independently owned CLI processes from pending test frames. macOS sampling remains bounded. No arbitrary locals, protocol payloads, credentials, results, production lifecycle behavior, assertions, or deadlines are changed.

The latest retained macOS evidence showed session.resume pending for 299.997 seconds while SDK/FFI locks were free and two native copilot-cabi threads remained blocked in pthread mutex acquisition. The exact released image is stripped and the release workflow retained no matching symbols, so native attribution remains blocked on matching symbols or a symbolized reproduction. No speculative SDK workaround is included.

Rust fixture publication

b5c9558f fixes the Windows host-crash fixture readiness race by writing and closing a sibling PID file before atomically renaming it to the observed path. The production SDK is unchanged. Deterministic filesystem regressions failed before and passed after; the complete native lifecycle group, nightly formatting, strict Clippy, and full cargo test --all-features passed with 895 tests and 45 existing ignores.

Go in-process E2E isolation

The earlier Go-only per-host Env capability and its four follow-up commits were removed from history. Go is again consistent with Node.js, Python, .NET, Java, and Rust at the public API boundary: ClientOptions.Env with InProcessConnection is rejected, and there are no changes to go/client.go, go/types.go, or go/README.md.

0b58a09a fixes the harness defect instead:

  • In-process E2E top-level tests run in fresh copies of the test binary.
  • Tests containing independent per-subtest environments use nested fresh workers.
  • Child-process-heavy MCP hook subtests also use nested workers, preventing repeated native/libuv SIGCHLD handler transitions in one race-enabled Go process.
  • Each worker establishes its environment and working directory before its first native host starts and never restores or mutates them afterward.
  • A deterministic guard rejects any later environment change.
  • Existing test selection, failures, skips, remaining deadlines, race instrumentation, and -test.gocoverdir counters are preserved.
  • The few tests that previously created a second proxy solely for isolation now use supported per-host BaseDirectory and token options while keeping the process-wide proxy stable.

Validation on Windows with race detection:

  • Public in-process Env rejection: passed.
  • Isolation argument, child success/failure/skip, and immutable-environment regressions: passed.
  • Nested-worker groups (AutoTier, ClientOptions, RPCMCPConfig, RPCServer, RpcServerMisc, RpcServerPlugins, PreMCPToolCallHook): passed.
  • Former multi-context groups (ClientAPI, RPCSessionState, StreamingFidelity): passed.
  • PreMCPToolCallHook also passed over the default stdio transport.
  • A complete in-process run exercised the entire package and found only two standalone-extension setup failures. The same two failures reproduce in the default transport and require extensionSdkPath because the local standalone runtime lacks the extension SDK assets; they occur before replay and are not caused by this change.
  • Full-tree tagged vet reaches the unchanged internal/ffihost/ffihost.go:334:31 unsafe-pointer warning; focused package tests and all modified-path checks pass.

Current state

Current head: 0b58a09a4f68e4192100c2d7e5276e4aea1ea23a. The prior head's Ubuntu in-process Go failure was a SIGCHLD/SA_ONSTACK crash while one worker repeatedly launched MCP child processes; the nested-worker change addresses that path. The prior .NET macOS CAPI failure occurred before test execution because GitHub returned HTTP 500 for the pinned CLI release download, so no .NET code change was warranted.

The complete current-head matrix is green, including all Go default/in-process cells on Ubuntu, macOS, Windows, and Alpine ARM64; all .NET CAPI cells; all Python native/stdio cells; all Rust default/in-process cells; and every required aggregate check. No jobs were manually rerun and no coverage was weakened. The PR remains open and unmerged.

Generated by Copilot

@stephentoub
stephentoub marked this pull request as ready for review September 15, 2026 13:54
@stephentoub
stephentoub requested a review from a team as a code owner September 15, 2026 13:54
Copilot AI balanced review requested due to automatic review settings September 15, 2026 13:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The focused changes appear coherent, but the required CI run still has three unresolved Rust failures.

Review tier: Balanced
Findings: None

What changed in this PR

Restores strict replay compatibility for both legacy one-image and newer multi-image CLI histories without changing SDK runtime behavior.

Changes:

  • Restores authentic image-bearing snapshot conversations while retaining pinned-CLI alternatives.
  • Adds HTTP replay coverage for accepted histories, final responses, and rejected mismatches.
File Description
test/​snapshots/​session_config/​vision_disabled_then_enabled_via_setmodel.yaml Adds the restored multi-image history.
test/​snapshots/​session_config/​should_accept_blob_attachments.yaml Adds the restored image-bearing attachment history.
test/​harness/​replayingCapiProxy.test.ts Tests strict replay acceptance and rejection behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for #2670 · copilot · sonnet50 · 39 AIC · ⌖ 13.3 AIC · ⊞ 8.3K

Comment thread go/client.go Outdated
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

stephentoub and others added 4 commits September 16, 2026 00:46
Restore authentic image-bearing recordings alongside exact one-image histories needed by the SDK-pinned CLI. Add strict HTTP replay coverage for both histories, completion, and rejected unrecorded image content.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep both legal completion-notification histories without changing hooks or matching. Require the final subagent answer followed by idle, and add scoped elapsed diagnostics for the still-unclassified resume timeout.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Discover independently owned CLI processes in pending test frames and retain failure-only diagnostics from both Python CI matrices. Preserve existing failures and test commands while collecting the native evidence needed to diagnose stalled session.resume responses.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Write and close a sibling file before publishing the readiness path. This prevents the lifecycle test from observing an empty or partial PID before terminating the host. Keep the process-liveness assertions and deadlines unchanged, and cover complete publication and failed writes with deterministic filesystem regressions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@stephentoub
stephentoub force-pushed the stephentoub-sdk-image-recordings branch from b70e444 to bdd7242 Compare September 16, 2026 04:51
@github-actions

Copy link
Copy Markdown
Contributor

SDK Consistency Review

I reviewed the changes in this PR against the six SDK implementations (Node.js/TypeScript, Python, Go, .NET, Java, Rust).

Scope of changes: All modified/added files are test infrastructure and CI/tooling only — no SDK client/public API source files were touched:

  • .github/workflows/python-sdk-tests.yml — CI diagnostics upload on failure
  • go/internal/e2e/** — Go E2E test isolation harness (inprocess_isolation.go, main_test.go) and updates to existing E2E tests to use it
  • python/e2e/timeout_diagnostics.py, python/test_timeout_diagnostics.py — Python E2E diagnostics helper
  • rust/tests/fixtures/pid_file.rs, rust/tests/pid_file_test.rs — Rust E2E PID-file test fixture, plus updates to existing E2E tests
  • test/harness/replayingCapiProxy.test.ts, test/harness/subagentHooksReplay.test.ts — shared replay-proxy harness test additions
  • test/snapshots/** — corresponding YAML snapshot updates

None of these changes add or modify a public SDK feature/API surface (nodejs/src/, python/copilot/, go/ client code, dotnet/src/, java/sdk/src/main/java/, rust/src/). They're per-language E2E test-isolation/diagnostics tooling, which is expected to differ between languages since each uses its own test runner and process model.

Conclusion: No cross-SDK API consistency issues found. This PR is out of scope for feature-parity review since it doesn't change any SDK client behavior.

Generated by SDK Consistency Review Agent for #2670 · copilot · sonnet50 · 21.1 AIC · ⌖ 12.2 AIC · ⊞ 8.3K ·

Run each in-process E2E test in a fresh worker and isolate multi-context subtests before starting native hosts. Keep the public in-process Env rejection unchanged while preventing native threads from racing process environment restoration.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@stephentoub
stephentoub force-pushed the stephentoub-sdk-image-recordings branch from bdd7242 to 0b58a09 Compare September 16, 2026 05:08
@stephentoub
stephentoub merged commit ff3ef75 into main Sep 16, 2026
106 checks passed
@stephentoub
stephentoub deleted the stephentoub-sdk-image-recordings branch September 16, 2026 13: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.

2 participants