Skip to content

Stop owned pytest processes when a TestPilot run is interrupted - #43

Merged
Jacob-Met merged 2 commits into
mainfrom
work/run-cancellation-18a24bf0c281-20261008
Oct 8, 2026
Merged

Jacob-Met merged 2 commits into
mainfrom
work/run-cancellation-18a24bf0c281-20261008

Conversation

@Jacob-Met

@Jacob-Met Jacob-Met commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Stop pytest when the caller cancels a run

Ctrl-C can return control to the caller while TestPilot's separately launched pytest process continues work. This change stops that owned POSIX process group when KeyboardInterrupt interrupts the initial process communication, then propagates cancellation. Closes #35.

Changes

  • Add one 15-line handler in testpilot/sandbox.py::_run: signal the owned process group, wait up to one second for its leader, close the output pipe and re-raise the interrupt. Cleanup errors do not replace the caller's cancellation.
  • Preserve the existing timeout implementation byte for byte, including its POSIX drain and Windows reader-thread behavior.
  • Add four real-process test cases for same-group children and escaped-session retained-pipe controls, plus run-cancellation documentation.

The source change is limited to this handler and its docstring. No parser, model, collector, output-writer, dependency or workflow change is included.

Preserved failure and native receiving

The original actual CLI witness on f3b2135cad31852b599350564fe76dd160a6a522 lets pytest write a work marker after its parent has exited on SIGINT. A barrier released only after parent exit establishes the ordering. The ordinary ScriptedModel control completes with two passing pytest cases and two accepted model entries. The first receiver's ledger.calls metadata error is retained; v2 corrects that interface to ledger.entries.

Candidate receiving uses the exact current-parent composition at 7f44c4f134f4d56d37dd14c7c2d2aaa3c8c8749e plus frozen source 649dc7d1fd02493af298876eea0697849404bba9. The original f3 execution is not relabeled as a newer-main run.

Receiving Original Candidate
Four frozen added process cases on current 7f44 1 passed, 3 failed 4 passed
Focused sandbox/timeout selection Original results retained 17 passed, 1 old optional-coverage case skipped
Author two-action actual CLI receiver 3 of 4 checks 4 of 4 checks
Independent generated-phase actual CLI receiver 12 of 14 checks 14 of 14 checks

The independent receiver interrupts generated-test verification after an actual existing-test pass and two accepted scripted replies. Its distinct handwritten fixture launches a finite worker inside pytest's process group. The original pytest and worker remain active and perform work after parent exit; the candidate leaves neither process active at parent exit and produces no later marker, without a receiver cleanup signal.

Four preexisting synthetic regular output sentinels retain exact bytes, inode, mode and write timestamps in both variants. They are preservation controls, not a claimed complete prior report. All 14 original and 16 candidate captured source files remain unchanged. The independent reviewer did not replay the author suite or ordinary control.

Native receiving used macOS arm64, Python 3.12.8 and pytest 9.1.1. Existing optional coverage was absent there; hosted full-suite qualification is separate.

Source composition and exact custody

The reviewed source branch is based on 362981dd84626fd55eda93584c2984cd9d0e5276, preserving PR41's completed-recheck HTML feature. The later review reproduces the entire prior recheck module by removing only its new writer block and imports. The renderer performs no work on import; the unchanged command boundary does not catch KeyboardInterrupt. This was a static composition review, with the earlier native results retaining their actual source attribution.

Root verified all four publication packets by byte count, SHA-256, Git blob and mode. The initial source tree 83adbdd85b2733367ec04e0218b825cafcfd1bd7 has 47 scoped paths: one parent file modified and 46 additions, with all 1,071 unrelated parent leaves preserved, for 1,118 total. All original controls, errors and evidence remain intact.

The explicit-target composition at eacb9907f51aeb9360da654c60cbbd7cf99ad877 and later main 513f17454af378ee042ccd29dc930ef332a48734 passed separate source interaction reviews. Later changes validate positive finite timeouts and complete diff hunks; both frozen actual-CLI fixtures remain admitted with the same changed function and line. The loop, sandbox, package configuration and workflow remain exact, and added diff-error catches do not intercept cancellation. Earlier native results retain their original source bindings.

Corrected process observation and hosted receiving

The corrected source head is 37985347ec7957902cc51223a0916b86ed5ef1f5, tree 0760ae5048eaee2177493081def4c7735d0aad65. Its sole test change adds -ww to the process-list command, plus an explanatory comment. The complete text and AST inverse recover the original test; all liveness, timing and cleanup assertions remain intact. Production source is unchanged.

The first hosted run passed 397 tests and 25 subtests but failed both new escaped-helper observations. On native Linux, the unchanged four-case group reproduced those two failures. Under pytest capture, ordinary ps returned only 80 columns while the independently identified helper remained alive; the wide form retained the full fixture path. The corrected four cases pass without diagnostic instrumentation. The original failure, standalone negative diagnosis, process identities, raw observations and corrected logs are retained in the 147-member evidence archive and receiving notes.

Hosted run 37833468085, job 113504469109, now passes 457 tests and 25 subtests on CPython 3.12.15 in 89.21 seconds. Root read the complete decoded log: 16,866 bytes, SHA-256 ee9b7b0e5407aaf3d9f56d6275c15859a36cf370f080c9b8e630e15b6df4f8fc.

The actual tested checkout is fc1793cb048693e082e5abf18f577212ebe3792c, with parents current main 513f17454af378ee042ccd29dc930ef332a48734 and the corrected head. Root verified its complete tree 29c33bd4dcd7d6d05d68e87b21530a7f3e69c3d8: all 52 contribution paths and 1,425 unrelated current-main leaves match exactly, for 1,477 leaves, with no conflict or omitted path.

Boundaries and integration gate

The qualification covers POSIX SIGINT during initial communication. Escaped sessions remain outside group cleanup. The one-second leader wait excludes Python's brief interrupt-handling wait and scheduling delays. Earlier test effects and model calls are not rolled back, and SIGKILL does not run test teardown. Pre-Popen interruption, other signals and native Windows cancellation remain unqualified.

The unchanged Ubuntu/Python 3.12 full pytest workflow must pass for the actual PR composition before merge. This source contribution does not install or deploy a runtime.

Preserve KeyboardInterrupt while bounding POSIX child cleanup. Include focused real-process tests, original negative controls, independent generated-phase receiving and exact current-parent reviews for issue #35.

Copy link
Copy Markdown
Owner Author

Additional native Linux receiving is now preserved at 66e5d7f4, based directly on unchanged offered head 95a3116c. This is an evidence-only branch, review/linux-cancellation-c77045b4; all 1,118 original leaves are unchanged, with four additions. Original source authorship and integration remain with estate 18a24bf0c281.

Receiver and exact source index · structured native result.

On native Linux/Python 3.14, an independently authored actual-process receiver passed all three candidate groups: ordinary stdout/stderr/nonzero exit, same-group SIGINT cleanup, and the escaped-session retained-pipe boundary. The current original sandbox passes only the ordinary control. In the same-group negative, both processes remain active after the interrupted runner exits and write markers when a barrier is released afterward. The candidate has neither active process nor later marker. The detached child remains outside group cleanup and exits naturally when released. Four prior-output sentinels retain exact bytes, inode, mode and write timestamps; no receiver cleanup signals were needed. Candidate sandbox blob 84330f96 reconstructs current cec50d8 byte for byte after removing only the original handler and restoring its docstring.

The full hosted gate remains failed. Existing job 113460074077 reports 397 passes and two authored escaped-child liveness failures on actual merge checkout c49cd45. This narrower acceptance does not override that gate. The exact test and log are retained. A separate long-command ps probe did not reproduce output truncation on this ThinkPad, so no cause is claimed or test correction proposed.

The complete 62-file archive is 36,822 bytes, SHA256 2b50e170422868037c5336eaeee64377df7acef57e5c6a50c612e5224f6b3732; all four published bodies were read back byte-exact. Initial ENOSPC/native-contract-freeze timing is explicit. No product, authored test, owner branch, runtime or service was changed.

Use ps -ww under pytest capture so owned escaped helpers remain observable. Preserve every liveness and timing assertion and the exact production cancellation handler. Retain the first hosted failure, native red/green evidence, diagnostic traces and complete root receiving proof.
@Jacob-Met
Jacob-Met merged commit 6c1c497 into main Oct 8, 2026
1 check passed

Copy link
Copy Markdown
Owner Author

Integrated and final receiving preserved by estate-18a24bf0c281.

PR43 merged as 6c1c4973f6d88c3cc3d120c76a3b6e30f9008bbb; issue #35 is closed. Actual parents are 513f17454af378ee042ccd29dc930ef332a48734 and corrected head 37985347ec7957902cc51223a0916b86ed5ef1f5. The actual merged tree 29c33bd4dcd7d6d05d68e87b21530a7f3e69c3d8 exactly matches hosted checkout fc1793cb048693e082e5abf18f577212ebe3792c: all 52 contribution paths and all 1,425 unrelated current-main leaves are preserved.

Run 37833468085, attempt 1, job 113504469109: 457 tests and 25 subtests passed on CPython 3.12.15, 89.21 seconds. The complete decoded log is 16,866 bytes, SHA256 ee9b7b0e5407aaf3d9f56d6275c15859a36cf370f080c9b8e630e15b6df4f8fc.

Final evidence is in receiving commit 4f49564ea24ba3d722b916ae2b72bf50d0e667a3, tree 103693d35fd7edfbc002fa9f179a640493f51429, on receiving/run-cancellation-final-ci-18a24bf0c281. It is rooted at the actual merge and adds 10 evidence files while preserving all 1,477 merged leaves, for 1,487 total. Root verified every leaf's mode/type/blob and read back the branch, commit, manifest and complete log exactly.

The original hosted failure remains in the merged ps-width-correction archive. Native diagnosis confirmed pytest's truncated ps observation, and the correction changed only the test helper to use ps -ww plus one comment. Production cancellation code was unchanged by that correction. The current-parent static receipt's then-pending hosted gate is satisfied by the exact combined-tree run above. Native Mac/ThinkPad provenance remains separate; no Windows, deployment or installed-runtime adoption is claimed.

@Jacob-Met
Jacob-Met deleted the work/run-cancellation-18a24bf0c281-20261008 branch October 19, 2026 02:58
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.

[claim] Stop the owned pytest process group when a TestPilot run is interrupted

1 participant