From ab700df8357f9f5bcb6473759751765b50633ba8 Mon Sep 17 00:00:00 2001 From: "Mars.P" Date: Tue, 13 Oct 2026 10:30:18 +0800 Subject: [PATCH] Make the timed-out salvage test outlast the launch it races The P2.2 salvage test gave its run a 1s wall deadline and expected the agent's write to land before the kill. The deadline is armed when the launch is bound, before the runner host's broker, exec bootstrap and guardian start and before /bin/sh runs. On a loaded host that launch alone took 1.0-2.0s, so the kill sometimes landed before the write and the test failed with an empty ChangedFiles. That read as broken salvage when salvage was never exercised. It failed this way twice on CI. The deadline is now 10s. The script echoes "written" once the file is on disk and only then sleeps, and the test reads that line back through the run's recorded events before it checks the capture. If a launch ever overruns the deadline again, the failure names the launch instead of blaming salvage. --- .../Workflows/AgentBranchPushFlowTests.cs | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/backend/tests/CodeSpace.IntegrationTests/Workflows/AgentBranchPushFlowTests.cs b/backend/tests/CodeSpace.IntegrationTests/Workflows/AgentBranchPushFlowTests.cs index def1b3d5d..e41a4f093 100644 --- a/backend/tests/CodeSpace.IntegrationTests/Workflows/AgentBranchPushFlowTests.cs +++ b/backend/tests/CodeSpace.IntegrationTests/Workflows/AgentBranchPushFlowTests.cs @@ -145,24 +145,36 @@ public async Task P2_2_a_timed_out_run_salvages_its_real_edit_as_a_real_pushed_b { // P2.2 end-to-end through the REAL completion pipeline (real Postgres, real git clone + authenticated push // against a real bare remote — only the codex/claude binary is faked): the scripted harness writes a REAL - // file to the clone, THEN sleeps past the 1s wall clock. CodeSpace force-kills it, but the write already - // reached disk before the kill — git ground truth, independent of the kill signal — so it must land as a - // real branch on the remote instead of vanishing with the killed process. + // file to the clone, says so on stdout, THEN sleeps far past its wall clock. CodeSpace force-kills it, but + // the write already reached disk before the kill — git ground truth, independent of the kill signal — so it + // must land as a real branch on the remote instead of vanishing with the killed process. + // + // The wall deadline is armed when the launch is bound, BEFORE the runner host's broker, exec bootstrap and + // guardian processes start and /bin/sh runs, so it has to outlast that launch on a loaded host. A 1s deadline + // did not: bind-to-script-start measured 1.0-2.0s on a 10x-oversubscribed host, and the kill landed before + // the write. The 10s deadline is comfortably above that; the "written" event makes any residual overrun read + // as what it is. if (OperatingSystem.IsWindows()) return; if (!await GitAvailableAsync()) return; + const int deadlineSeconds = 10; + var teamId = await SeedTeamAsync(); using var remote = new BareRemote(); await remote.SeedWithOneCommitAsync(); var repoId = await SeedBoundRepositoryAsync(teamId, remote.Url, defaultBranch: "main"); - var runId = await CreateRepoRunAsync(teamId, repoId, pushProducedBranch: true, timeoutSeconds: 1); + var runId = await CreateRepoRunAsync(teamId, repoId, pushProducedBranch: true, timeoutSeconds: deadlineSeconds); - await ExecuteAsync(runId, new ScriptedHarness("printf 'salvaged edit\\n' > salvaged.txt; sleep 10")); + await ExecuteAsync(runId, new ScriptedHarness("printf 'salvaged edit\\n' > salvaged.txt && echo written; sleep 300")); using var scope = _fixture.BeginScope(); - var run = await scope.Resolve().GetAsync(runId, CancellationToken.None); - run.Status.ShouldBe(AgentRunStatus.TimedOut); + var service = scope.Resolve(); + var run = await service.GetAsync(runId, CancellationToken.None); + run.Status.ShouldBe(AgentRunStatus.TimedOut, "the wall deadline, not the script, ended the run — the sleep outlasts it by minutes"); + + var events = await service.GetEventsAsync(runId, teamId, 0, CancellationToken.None); + events.Select(e => e.Text).ShouldContain("written", $"the agent's write completed before the kill. Missing means the launch took longer than the {deadlineSeconds}s deadline and the agent never reached its edit — the test's precondition failed on an overloaded host, not salvage"); var expectedBranch = AgentRunExecutor.BuildBranchName(runId); var result = JsonSerializer.Deserialize(run.ResultJson!, AgentJson.Options)!;