Skip to content

fix(sandbox): settle PTY cleanup before cancellation - #4751

Open
mikemikimike wants to merge 9 commits into
openai:mainfrom
mikemikimike:fix/pty-cleanup-cancellation-4747
Open

mikemikimike wants to merge 9 commits into
openai:mainfrom
mikemikimike:fix/pty-cleanup-cancellation-4747

Conversation

@mikemikimike

@mikemikimike mikemikimike commented Aug 29, 2026

Copy link
Copy Markdown

This pull request fixes sandbox PTY cleanup ownership and cancellation races, and ensures PTY cleanup batches attempt every detached entry after an individual failure.

Summary

When a PTY is removed from the session registry before asynchronous teardown, caller cancellation can leave cleanup without an owner. This change keeps cleanup owned until detached teardown settles. pty_terminate_all() also attempts every detached entry and reports the first cleanup error after the batch.

Changes

  • Keep PTY cleanup ownership with the session until detached teardown settles.
  • Preserve caller cancellation and cleanup errors while allowing owned PTY cleanup to complete within bounded waits.
  • Ensure a failure in one cleanup entry does not prevent attempts on the remaining entries.
  • Bound Docker PTY kill/exec waits while preserving local resource cleanup ordering.
  • Apply the focused ownership and cancellation handling across the sandbox backends.
  • Add focused regression coverage for cancellation, bounded cleanup, and attempt-all batching.

Validation

  • BaseSandboxSession focused PTY/cleanup tests: 30 passed, 2 deselected.
  • Docker focused PTY/deferred-cleanup tests: 19 passed, 98 deselected.
  • Blaxel focused PTY ownership/cancellation tests: 9 passed, 245 deselected.
  • tests/sandbox/test_runtime.py was not run on Windows because UnixLocalSandbox is intentionally unsupported there.
  • GitHub currently reports no hosted checks for this fork head.

The changes are internal and do not add a public result lifecycle API, checkpoint state machinery, or runner/computer lifecycle changes.

Fixes #4747

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ffa2214289

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/sandbox/sandboxes/unix_local.py Outdated

This comment was marked as off-topic.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

Reviewed commit: 8fc7aab210

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

fscfede-beep

This comment was marked as off-topic.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3e9f727088

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/sandbox/session/base_sandbox_session.py Outdated
@mikemikimike

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: 9ce15f766d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: 9ce15f766d

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 68036ee68d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tests/sandbox/test_base_sandbox_session.py Outdated
Comment thread tests/extensions/sandbox/test_blaxel.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0708d65538

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tests/sandbox/test_base_sandbox_session.py Outdated
@mikemikimike

Copy link
Copy Markdown
Author

当前 head 已在 0708d65 保留调用方 cancellation reason,并补充版本兼容、资源清理和聚焦测试覆盖。关闭这些已处理线程。

@sylvesterkaczmarek sylvesterkaczmarek 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.

pty_terminate_all() clears the registry before cleanup_all() runs, but cleanup_all() stops on the first _terminate_pty_entry exception. Any later PTYs are then left alive with no registry owner and cannot be retried. Could we attempt cleanup for every entry, preserve/re-raise the first error afterward, and add a first-cleanup-fails regression?

Copy link
Copy Markdown

The new review is valid against current head 16960bd2: each backend's pty_terminate_all() clears registry ownership first, then its sequential cleanup_all() still aborts on the first _terminate_pty_entry() exception.

A fail-closed shape that preserves the existing cancellation settlement contract is:

async def cleanup_all() -> None:
    first_error: BaseException | None = None
    for entry in entries:
        try:
            await self._terminate_pty_entry(entry)
        except BaseException as error:
            if first_error is None:
                first_error = error
    if first_error is not None:
        raise first_error

Then keep await self._settle_pty_cleanup(cleanup_all()) unchanged. This ensures all de-owned PTYs receive a cleanup attempt before the first cleanup error is re-raised. The regression should use two entries where the first termination raises and the second records successful cleanup; assert both attempts occurred, the registry remains empty, and the first exception is propagated.

I have verified this is still required by inspection of the current PR diff; I have not executed the upstream test matrix or modified the author's branch.

@mikemikimike

mikemikimike commented Sep 3, 2026

Copy link
Copy Markdown
Author

Addressed in commit 10d6f2d. PTY batch cleanup now attempts every detached entry, re-raises the first cleanup failure after the batch, and all seven providers use the shared typed helper for prune, finalizer, and registry-clear cleanup. The adjacent unregistered-startup changes were removed to keep this PR focused on #4747. Added base-session, Blaxel registry-clear cancellation, and Blaxel first-cleanup-fails regressions. Validation: focused sandbox tests 254 passed, 1 skipped; mypy 308 source files passed; Pyright passed; Ruff format/lint and git diff --check passed. The full local suite remains limited by unrelated Windows symlink privileges, MCP 502, skills workspace state, and example encoding issues. No GitHub checks are currently reported for this branch.

This comment was marked as off-topic.

@chatgpt-codex-connector

This comment was marked as off-topic.

fscfede-beep

This comment was marked as off-topic.

sylvesterkaczmarek

This comment was marked as off-topic.

@seratch seratch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the update. There is still a shutdown-liveness issue in the Docker path.

_settle_pty_cleanup() suppresses caller cancellation until cleanup finishes, but Docker's _terminate_pty_entry() awaits kill/PID-file cleanup through exec_run() without a deadline. If that attached exec stream stalls, cancellation no longer releases the caller, the entry socket is never closed, and later entries in the cleared batch are never attempted. An outer asyncio.wait_for() also waits for cancellation to finish.

Please bound the Docker backend waits and ensure local resource cleanup and subsequent batch entries still run when that deadline expires. Add a regression through the Docker cleanup path with a stalled backend operation, asserting bounded completion and resource cleanup. The existing cancellation tests always release the blocked operation, so they do not cover this case.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 54a942db5f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/sandbox/session/base_sandbox_session.py
Comment thread src/agents/sandbox/sandboxes/docker.py Outdated
Comment thread tests/sandbox/test_docker.py Outdated
fscfede-beep

This comment was marked as off-topic.

This comment was marked as off-topic.

This comment was marked as off-topic.

This comment was marked as off-topic.

@chatgpt-codex-connector

This comment was marked as off-topic.

This comment was marked as off-topic.

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 725c7e796a

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 725c7e796a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +710 to +711
if not self._cleanup_finished:
return False

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Allow later sessions to delete before manager finalization

When a run hands off across at least two SandboxAgents and an earlier resource cleanup marks its backend for preservation—for example, after a stop, snapshot, or delete failure—a later resource reaches _wait_before_backend_delete() while the manager is still iterating. Because the preserved session prevents the early success return and _cleanup_finished is only set after that loop, this callback returns False forever; the later cleanup spins, so Runner.run() never finishes cleanup. Permit this in-loop delete checkpoint to proceed, or publish the preserved state before awaiting the next resource.

AGENTS.md reference: AGENTS.md:L117-L118

Useful? React with 👍 / 👎.

Comment thread src/agents/sandbox/runtime.py Outdated
Comment on lines +122 to +124
self._session_manager.register_resume_state_observer(
lambda resume_state: setattr(result, "_sandbox_resume_state", resume_state)
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep emitted RunState checkpoints synchronized with late snapshots

When cleanup returns while a fallback snapshot is still pending, a caller can immediately use the documented result.to_state() API, which deep-copies the current _sandbox_resume_state. This observer updates only the result after the late snapshot succeeds; the already emitted RunState keeps the previous snapshot and backend reference even though deferred cleanup can then delete that backend, so resuming the checkpoint can restore stale workspace contents. Fresh evidence beyond the earlier late-resume-state comments is this independently copied public checkpoint carrier; either keep emitted checkpoints updated or prevent backend deletion until their recovery payload is definitive.

AGENTS.md reference: AGENTS.md:L117-L119

Useful? React with 👍 / 👎.

@mikemikimike
mikemikimike marked this pull request as draft September 16, 2026 06:04
@mikemikimike

Copy link
Copy Markdown
Author

@codex review

@mikemikimike
mikemikimike marked this pull request as ready for review September 16, 2026 12:32

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4588b73cb1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/result.py Outdated
auto_previous_response_id=self._auto_previous_response_id,
)
_copy_pending_nested_agent_tool_states(state, self)
self._sandbox_state_checkpoints.append(weakref.ref(state))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep persisted checkpoints valid through late snapshots

When cleanup returns while a fallback snapshot is still pending, a caller can immediately persist result.to_state().to_json(). Registering a weak reference here lets the later observer update only the live RunState; it cannot update the already returned JSON or a copy written to storage. Deferred cleanup can then publish the newer snapshot and delete the backend, leaving that persisted checkpoint pointing to a deleted backend with stale workspace contents. Fresh evidence beyond the earlier checkpoint comment is that the new weak-reference mechanism explicitly covers only live objects; backend deletion or checkpoint exposure must wait until the recovery payload is definitive.

AGENTS.md reference: AGENTS.md:L117-L117

Useful? React with 👍 / 👎.

Comment on lines +765 to +769
if self._preserved_backend_retry_required and any(
resources._has_retryable_open_dependencies()
for resources in self._resources_by_agent.values()
):
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Release runner ownership when cleanup cannot be retried

When a runner-owned session has an open factory-backed snapshot dependency and both stop() and its immediate fallback snapshot fail, _preserved_backend_retry_required is set and this branch retains the session, dependencies, and SandboxAgent concurrency guard for a future manager.cleanup() retry. That retry is available only in helper-level tests: Runner.run() does not expose its manager and clears result._sandbox_session, so after returning the recovery state the manager is discarded while the guard remains permanently active. Reusing or resuming with the same public agent then fails the concurrent-run check, and the retained provider resources may remain live; transfer or release ownership at the public runner boundary instead of waiting for an inaccessible retry.

AGENTS.md reference: AGENTS.md:L118-L118

Useful? React with 👍 / 👎.

@mikemikimike
mikemikimike marked this pull request as draft September 16, 2026 12:47
@mikemikimike

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: c70f7da8ca

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c70f7da8ca

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/result.py Outdated
Comment on lines +426 to +429
cleanup = self._sandbox_cleanup
if cleanup is None:
return
await cleanup()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Join active streamed cleanup before retrying

When a streamed run is cancelled while the callback-started _run_sandbox_cleanup() is still pending, RunResultStreaming inherits this method and await result.aclose() invokes the cleanup closure a second time instead of awaiting _sandbox_cleanup_task. Both closures enqueue the memory result and enter the same SandboxRuntimeSessionManager.cleanup() concurrently; the later invocation can overwrite the first invocation's published resume state or race backend finalization. Join the existing streaming cleanup task before starting a retry.

AGENTS.md reference: AGENTS.md:L117-L117

Useful? React with 👍 / 👎.

Comment thread src/agents/run_state.py Outdated
Comment on lines +873 to +874
_sandbox_resume_state_pending: bool = field(default=False, repr=False)
"""Whether sandbox cleanup may still replace the resume payload."""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject RunState while sandbox cleanup is pending

The pending flag is enforced by to_state() and to_json(), but both Runner.run() entry paths accept an existing RunState without checking it. A caller can create a checkpoint after an incomplete cleanup, start result.aclose() (which marks that live checkpoint pending), and concurrently resume the checkpoint; the new run then reattaches from stale state while the retry may stop or delete the same backend. Reject direct resume until _sandbox_resume_state_pending clears, not only serialization.

AGENTS.md reference: AGENTS.md:L117-L117

Useful? React with 👍 / 👎.

@mikemikimike

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 3373f05c7c

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3373f05c7c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/sandbox/runtime.py Outdated
Comment on lines +218 to +220
result._update_sandbox_resume_state(
payload if self.runner_ownership_requires_transfer else None,
pending=self.cleanup_has_pending_work,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve serialized checkpoints across cleanup retries

When initial cleanup preserves a backend without pending work, callers can validly persist result.to_state().to_json() and later invoke the new result.aclose() retry. If that retry succeeds, the manager deletes the backend and this branch clears only the live result and weakly referenced RunState objects; the already persisted JSON still points to the deleted backend and may contain the snapshot from before the successful retry. Fresh evidence beyond the earlier late-snapshot comment is this explicit retry path, which starts after serialization was allowed; keep the emitted durable checkpoint resumable rather than deleting its backend behind it.

AGENTS.md reference: AGENTS.md:L117-L119

Useful? React with 👍 / 👎.

Comment thread src/agents/run_internal/sync.py Outdated
Comment on lines +126 to +127
if threading.current_thread() is not self.thread:
self.thread.join()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Bound sync-driver handoff during generator shutdown

When a prior SDK-owned run_sync() leaves deferred cleanup, the driver eventually enters shutdown_asyncgens(); if an async-generator finalizer waits indefinitely, the next run_sync() calls stop(), whose callback deliberately refuses to stop that loop once generator shutdown has begun, and this unbounded join() therefore hangs the caller forever. Bound this handoff or retain an independently driven owner without synchronously joining a potentially stuck finalizer.

AGENTS.md reference: AGENTS.md:L117-L118

Useful? React with 👍 / 👎.

Comment on lines +441 to +443
snapshot_error = await self._persist_snapshot_before_stop_error(
before_stop_error=before_stop_error
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid snapshotting after failed PTY termination

When _before_stop() fails because PTY termination completed with an error after pty_terminate_all() removed its registry entry—for example, a Docker kill command fails—_has_pending_pty_cleanup_tasks() is false even though the process may still be alive, so this fallback starts snapshotting immediately. The surviving process can mutate files during the archive, and later cleanup retries cannot find the removed entry to terminate it, leaving the persisted workspace inconsistent; a completed cleanup task is not proof that termination succeeded, so retain the failed entry or stop the backend before snapshotting.

AGENTS.md reference: AGENTS.md:L117-L119

Useful? React with 👍 / 👎.

@mikemikimike

Copy link
Copy Markdown
Author

@codex review

1 similar comment
@mikemikimike

Copy link
Copy Markdown
Author

@codex review

@jbeckwith-oai jbeckwith-oai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The original detached-PTY cleanup issue is real: removing entries before asynchronous teardown can leave cleanup unowned when cancellation interrupts it, and a batch failure must not prevent attempts on the remaining entries.

Please reset this PR to that scope. The current implementation adds a public result.aclose() lifecycle, checkpoint retention/late-state machinery, generic computer-disposal deadlines, and a new run_sync loop driver with caller-surface heuristics. Those are substantive API and architecture changes beyond the PTY cleanup fix. Keep the focused PTY ownership/cancellation and attempt-all batching behavior; remove the broader runner/result/computer lifecycle redesign from this PR.

In particular, restore the released foreground run_sync loop selection. The new default selects an SDK loop despite an existing caller policy loop, bypassing its task factory/supervision and changing loop affinity. It can also reuse the process-global HTTP client's pooled transports across those two loops. More surface-detection exceptions would expand the same problem; preserving the existing foreground loop contract is the smaller correction.

Reviewed 195b4bdb93c31441738fa92a6f856fff93721131 against its merge base and the released loop behavior, including independent correctness and architecture reviews. All runtime changes and representative regression coverage were inspected; this is sufficient to request the scope reset, not a claim of exhaustive validation of the expanded architecture. GitHub reports no hosted checks, and no local runtime tests were executed.

Comment thread src/agents/run.py Outdated
# Keep unbound synchronous runs on an SDK-owned loop so a caller's default loop remains
# stopped and closable. Explicit caller-owned run surfaces may already own primitives on
# that default loop, so run the foreground coroutine there when one is available.
sync_loop = _get_sync_loop()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Preserve the caller-selected foreground event loop

A plain run_sync now chooses a separate SDK loop even when the caller installed an open default loop. This bypasses that loop's configured task factory and supervision, unlike the deliberately tested behavior in v0.22.2. Adding context/hooks/session on a later call can switch back to the caller loop while OpenAIProvider still retains the process-global HTTP pool, creating a cross-loop transport path. Please restore the existing foreground loop selection as part of narrowing this PR to PTY cleanup, rather than adding more caller-surface heuristics.

@mikemikimike
mikemikimike force-pushed the fix/pty-cleanup-cancellation-4747 branch from f7f55a8 to 2e9e1e4 Compare September 17, 2026 17:04
@mikemikimike
mikemikimike marked this pull request as ready for review September 17, 2026 17:58
@mikemikimike
mikemikimike requested a review from a team as a code owner September 17, 2026 17:58
@mikemikimike
mikemikimike force-pushed the fix/pty-cleanup-cancellation-4747 branch from e890a5b to bb88424 Compare September 18, 2026 15:19
@mikemikimike

Copy link
Copy Markdown
Author

Reworked this branch onto upstream main at fdf21db6 and reset the PR to the requested PTY-only scope.

Removed the broader runner/result/computer lifecycle redesign and restored the released run_sync loop behavior. The current head is bb884245c1482ec8dc3b33ac14931874abb1d410 and the diff is now limited to 10 PTY-related files (335 additions, 28 deletions), covering:

  • PTY cleanup ownership after registry removal;
  • cancellation propagation after cleanup settles;
  • attempt-all batch cleanup with first-error propagation;
  • provider cleanup coverage and regression tests.

Verification on the rewritten head:

  • base PTY cleanup tests: 5 passed;
  • Blaxel + Docker tests: 356 passed, 1 skipped;
  • 5 Docker symlink tests remain blocked by this Windows host's missing symlink privilege (WinError 1314);
  • Ruff format and lint passed for all changed files;
  • git diff --check passed.

/re-review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants