diff --git a/dotnet/test/E2E/RpcShellAndFleetE2ETests.cs b/dotnet/test/E2E/RpcShellAndFleetE2ETests.cs index a51fc7daea..2946b7bbee 100644 --- a/dotnet/test/E2E/RpcShellAndFleetE2ETests.cs +++ b/dotnet/test/E2E/RpcShellAndFleetE2ETests.cs @@ -28,7 +28,7 @@ public async Task Should_Execute_Shell_Command() [Fact] public async Task Should_Kill_Shell_Process() { - var session = await CreateSessionAsync(); + await using var session = await CreateSessionAsync(); var command = OperatingSystem.IsWindows() ? "powershell -NoLogo -NoProfile -Command \"Start-Sleep -Seconds 30\"" : "sleep 30"; diff --git a/go/internal/e2e/rpc_shell_and_fleet_e2e_test.go b/go/internal/e2e/rpc_shell_and_fleet_e2e_test.go index d7ffd725d7..81b8471dac 100644 --- a/go/internal/e2e/rpc_shell_and_fleet_e2e_test.go +++ b/go/internal/e2e/rpc_shell_and_fleet_e2e_test.go @@ -55,6 +55,7 @@ func TestRPCShellAndFleetE2E(t *testing.T) { if err != nil { t.Fatalf("Failed to create session: %v", err) } + t.Cleanup(func() { _ = session.Disconnect() }) var command string if runtime.GOOS == "windows" { diff --git a/python/pyproject.toml b/python/pyproject.toml index a867fe51dd..7e6274d9c0 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -92,3 +92,7 @@ python_files = "test_*.py" python_classes = "Test*" python_functions = "test_*" asyncio_mode = "auto" +# Bound every test so a deadlock fails fast with a stack dump instead of occupying the +# whole CI leg until GitHub's 6-hour job limit. The full suite runs in ~10 minutes, so no +# individual test legitimately approaches this. +timeout = 300 diff --git a/rust/tests/e2e/rpc_mcp_lifecycle.rs b/rust/tests/e2e/rpc_mcp_lifecycle.rs index 028b5a527f..aa3adcf5c3 100644 --- a/rust/tests/e2e/rpc_mcp_lifecycle.rs +++ b/rust/tests/e2e/rpc_mcp_lifecycle.rs @@ -194,58 +194,11 @@ async fn should_start_and_restart_mcp_server() { .await; } -#[tokio::test] -async fn should_register_and_unregister_external_mcp_client() { - with_e2e_context( - "rpc_mcp_lifecycle", - "should_register_and_unregister_external_mcp_client", - |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let host_server = "rpc-lifecycle-extclient-host"; - let client = ctx.start_client().await; - let session = - client - .create_session(ctx.approve_all_session_config().with_mcp_servers( - create_test_mcp_servers(ctx.repo_root(), host_server), - )) - .await - .expect("create session"); - wait_for_mcp_server_status(&session, host_server, McpServerStatus::Connected).await; - - let external_name = "rpc-lifecycle-external-client"; - assert!(!is_mcp_server_running(&session, external_name).await); - - call_session_rpc( - &session, - "session.mcp.registerExternalClient", - json!({ - "serverName": external_name, - "client": { "id": external_name }, - "transport": { "kind": "in-process" }, - "config": { "command": "noop" } - }), - ) - .await - .expect("register external MCP client"); - assert!(is_mcp_server_running(&session, external_name).await); - - call_session_rpc( - &session, - "session.mcp.unregisterExternalClient", - json!({ "serverName": external_name }), - ) - .await - .expect("unregister external MCP client"); - assert!(!is_mcp_server_running(&session, external_name).await); - - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }, - ) - .await; -} +// There is deliberately no e2e test for `session.mcp.registerExternalClient`. That method is +// marked `visibility: internal` in the shared API contract: its `client` and `transport` fields +// are live in-process MCP SDK instances, so it cannot be driven over JSON-RPC, and no SDK +// exposes it as a typed method. A raw-RPC test used to pass only because older CLIs routed +// internal methods generically; it never exercised a supported wire API. #[tokio::test] async fn should_reload_mcp_servers_with_config() { diff --git a/scripts/codegen/rust.ts b/scripts/codegen/rust.ts index a04f5a21c5..127dc2e48d 100644 --- a/scripts/codegen/rust.ts +++ b/scripts/codegen/rust.ts @@ -575,6 +575,45 @@ function emitRustMapAlias( ); } +/** + * Map a primitive JSON Schema type to its Rust equivalent, or `undefined` when + * the schema is not a plain scalar. Mirrors the primitive branches of + * {@link resolveRustType}. + */ +function rustScalarType(schema: JSONSchema7): string | undefined { + if (schema.enum || schema.const !== undefined) return undefined; + switch (schema.type) { + case "string": + return "String"; + case "number": + return "f64"; + case "integer": + return isIntegerSchemaBoundedToInt32(schema) ? "i32" : "i64"; + case "boolean": + return "bool"; + default: + return undefined; + } +} + +/** + * Emit a type alias for a named schema that resolves to a primitive scalar + * (e.g. an RPC result declared as `{ "type": "integer" }`). Without this the + * generated RPC surface would reference a `*Result` type that was never + * defined. + */ +function emitRustScalarAlias( + typeName: string, + schema: JSONSchema7, + ctx: RustCodegenCtx, + description?: string, +): void { + if (ctx.generatedNames.has(typeName)) return; + const scalarType = rustScalarType(schema); + if (!scalarType) return; + emitRustTypeAlias(typeName, schema, scalarType, ctx, description); +} + function rustRpcResultDescription( method: RpcMethod, resultSchema: JSONSchema7 | undefined, @@ -1527,6 +1566,8 @@ function generateApiTypesCode( } else { tryEmitRustUnion(schema, name, "", ctx); } + } else { + emitRustScalarAlias(name, schema, ctx, schema.description); } } @@ -1576,6 +1617,8 @@ function generateApiTypesCode( emitRustMapAlias(resultName, resolved, ctx, resolved.description); } else if (isObjectSchema(resolved)) { emitRustStruct(resultName, resolved, ctx, resolved.description); + } else { + emitRustScalarAlias(resultName, resolved, ctx, resolved.description); } } } diff --git a/test/snapshots/rpc_mcp_lifecycle/should_register_and_unregister_external_mcp_client.yaml b/test/snapshots/rpc_mcp_lifecycle/should_register_and_unregister_external_mcp_client.yaml deleted file mode 100644 index 056351ddb4..0000000000 --- a/test/snapshots/rpc_mcp_lifecycle/should_register_and_unregister_external_mcp_client.yaml +++ /dev/null @@ -1,3 +0,0 @@ -models: - - claude-sonnet-4.5 -conversations: []