Skip to content

feat(sandbox): add Aliyun AgentRun sandbox client#3732

Closed
iridescentWen wants to merge 1 commit into
openai:mainfrom
iridescentWen:feat/aliyun-sandbox
Closed

feat(sandbox): add Aliyun AgentRun sandbox client#3732
iridescentWen wants to merge 1 commit into
openai:mainfrom
iridescentWen:feat/aliyun-sandbox

Conversation

@iridescentWen

Copy link
Copy Markdown

Summary

Adds an Aliyun (AgentRun) sandbox backend for the Agents SDK sandbox extension,
built on the async agentrun-sdk (agentrun-sdk==0.0.35, new optional aliyun
extra). It mirrors the existing Vercel backend lifecycle (create / resume /
delete / SandboxSession context manager) so callers can swap backends without
code changes.

Highlights:

  • Async AgentRun API throughout (create_sandbox_async, cmd_async,
    download_async/upload_async, check_health_async, delete_async,
    get_sandbox_async) — no blocking to_thread wrappers.
  • Production hardening: gateway HTTP 502 retry with backoff, the 30s
    /processes/cmd hard cap, and a resolve-workspace-path empty-stdout retry.
  • running() uses the cheap health probe; adds append(), a 100MB upload guard,
    and OSS / NAS / PolarFs mount configs passed straight through to
    create_sandbox_async.
  • resume() reattaches an existing sandbox via get_sandbox_async and falls back
    to a fresh sandbox + snapshot rehydration when it is gone.
  • Credentials are supplied through the public AliyunSandboxClientOptions
    (or AliyunSandboxClient defaults) and resolved into an agentrun Config;
    serialized session state never carries secrets.
  • Exposed under agents.extensions.sandbox behind the optional aliyun extra
    (lazy import guarded), with an example runner and reference docs.

Test plan

  • New unit suite tests/extensions/sandbox/test_aliyun.py (72 cases) mocks the
    agentrun-sdk via sys.modules injection, so it needs no real package and
    never touches Alibaba Cloud. Covers user-arg rejection, exec normalization,
    502/timeout handling, file IO, append, tar validation, health-based
    running(), shutdown/delete, workspace persist/hydrate, credential
    threading, resume reattach + fallback, and env injection. Backend module
    coverage is 92%.
  • make format, make lint, make typecheck (mypy + pyright, 0 errors), and
    make tests (4920 passed, 1 skipped) all pass locally.

Issue number

N/A

Checks

  • I've added new tests, if relevant
  • I've run .agents/skills/code-change-verification/scripts/run.sh
  • I've confirmed all verification steps pass
  • If using Codex, I've run /review before submitting this PR

@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: a8509928d2

ℹ️ 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 +326 to +327
if self.state.env:
await self._inject_user_env(self.state.env)

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 Inject manifest environment variables

When callers provide environment values through Manifest(environment=...), the Aliyun backend never resolves or injects them because this branch only writes state.env from client options. Other sandbox clients merge manifest.environment.resolve() into the runtime environment, so Aliyun agents and shell commands silently miss manifest-provided env vars/secrets. Please merge the resolved manifest environment with state.env before calling _inject_user_env.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch — fixed in 23c95aec. _prepare_backend_workspace now resolves self.state.manifest.environment.resolve() and merges it with the option-provided state.env (manifest wins on key collisions, None values dropped) before _inject_user_env, matching the other backends. Added test test_prepare_backend_workspace_merges_manifest_env.

Comment thread src/agents/extensions/sandbox/aliyun/sandbox.py
@iridescentWen iridescentWen force-pushed the feat/aliyun-sandbox branch from a850992 to 23c95ae Compare July 6, 2026 02:29
@iridescentWen

Copy link
Copy Markdown
Author

@codex review

Addressed both P2 findings in 23c95aec:

  • Manifest env: _prepare_backend_workspace now merges manifest.environment.resolve() with state.env before injection.
  • Health wait: _ensure_sandbox polls check_health_async until the sandbox is ready (up to DEFAULT_ALIYUN_WAIT_FOR_RUNNING_TIMEOUT_S) after create, raising WorkspaceStartError on timeout.

New tests added and make format/lint/typecheck/tests all pass.

@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: 23c95aecda

ℹ️ 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".

options: AliyunSandboxClientOptions,
) -> SandboxSession:
resolved_manifest = _resolve_manifest_root(manifest)
region = options.region or self._region or DEFAULT_ALIYUN_REGION

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 Honor client-level region defaults

When callers configure AliyunSandboxClient(region="cn-shanghai") and pass default AliyunSandboxClientOptions(), this expression always selects the options default (cn-hangzhou) before the client value, unlike the credential fallback logic below. That creates the sandbox and serialized state in the wrong region unless every caller redundantly sets options.region; treat the options default as unset or make the field optional so the client-level default can take effect.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in dab23951. AliyunSandboxClientOptions.region is now str | None = None, and create resolves it via _resolve_credential(options.region, self._region) or DEFAULT_ALIYUN_REGION — same is not None precedence as the credential fallback — so a client-level region now takes effect when options leaves it unset. Added test_client_create_honors_client_region_default and test_client_create_options_region_overrides_client.

Comment on lines +547 to +549
env_lines = "\n".join(
f"export {k}={shlex.quote(v)}" for k, v in env.items() if v is not None
)

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 Validate env names before writing shell exports

For manifests or options.env whose keys are not fully trusted, the values are quoted but the variable names are emitted raw into a file that is later sourced by every sh -lc command. A key such as X; curl ... # turns the generated export line into arbitrary shell commands inside the sandbox, potentially alongside manifest-provided secrets; reject non-shell-identifier names or write the environment without sourcing unescaped keys.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in dab23951. _inject_user_env now validates each key against ^[A-Za-z_][A-Za-z0-9_]*$ before writing the export line; non-identifier names (e.g. X; curl ... #) are skipped with a warning rather than emitted raw into the sourced file. Added test_inject_user_env_skips_unsafe_names and test_inject_user_env_all_names_unsafe_is_noop. (This one was exposed by the manifest-env fix above — good catch.)

@iridescentWen iridescentWen force-pushed the feat/aliyun-sandbox branch from 23c95ae to dab2395 Compare July 6, 2026 02:47
@iridescentWen

Copy link
Copy Markdown
Author

@codex review

Addressed both P2 findings from the last pass in dab23951:

  • Region default: options.region is now optional (None) and resolved with the same is not None precedence as credentials, so AliyunSandboxClient(region=...) takes effect.
  • Env name safety: _inject_user_env now rejects non-identifier variable names before writing shell exports.

New tests added; make format/lint/typecheck/tests all pass.

@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: dab2395153

ℹ️ 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 +566 to +568
f"cat > {_AGENTS_ENV_FILE} <<'OPENAI_AGENTS_ENV_EOF'\n"
f"{env_lines}\n"
"OPENAI_AGENTS_ENV_EOF\n"

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 injected env secrets out of snapshots

When manifest/options env contains secrets and the session uses the default root /home/user, this writes $HOME/.openai_agents_env inside the persisted workspace. persist_workspace() tars . from that root, so stop()/snapshotting includes the export file and leaks secret values into snapshot storage and resumed workspaces. Put this file outside the persisted root or register it as a persistence skip path before snapshotting.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in dc63eaa5. _inject_user_env now registers the secret-bearing .openai_agents_env file (which lives at $HOME, i.e. the default workspace root) as a persist-workspace skip path via register_persist_workspace_skip_path, so persist_workspace() adds --exclude=./.openai_agents_env and the secrets never land in snapshots or resumed workspaces. .profile only gets a non-secret source line, so it may persist. Added test_inject_user_env_excluded_from_snapshot.

self.state.sandbox_id,
effective_cwd,
timeout_int,
shell_cmd[:500],

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 Stop logging injected environment values

When _prepare_backend_workspace() injects manifest/options env, _inject_user_env() passes a shell script containing export KEY=<value> through _run_command(). Logging shell_cmd at INFO records those env values in normal application logs, including secrets from Manifest.environment; redact the command for env injection or avoid logging command payloads that can contain secrets.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in dc63eaa5. _run_command gained a redact_command flag; _inject_user_env calls it with redact_command=True so the INFO log records cmd='<redacted>' instead of the export KEY=<secret> payload. The command still executes normally against the sandbox. Added test_inject_user_env_does_not_log_secret.

@iridescentWen iridescentWen force-pushed the feat/aliyun-sandbox branch from dab2395 to dc63eaa Compare July 6, 2026 03:03
@iridescentWen

Copy link
Copy Markdown
Author

@codex review

Addressed both P1 secret-leak findings in dc63eaa5:

  • Snapshot leak: the .openai_agents_env file is now registered as a persist-workspace skip path, so persist_workspace() excludes it from snapshots.
  • Log leak: _run_command gained a redact_command flag; env injection uses it so export KEY=<secret> no longer appears in logs.

New tests added; make format/lint/typecheck/tests all pass.

@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: dc63eaa51d

ℹ️ 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".

# snapshots so secrets never land in persisted/resumed workspaces. (`.profile`
# only gets a `source` line + marker, no secrets, so it may persist.)
try:
self.register_persist_workspace_skip_path(_AGENTS_ENV_REL_PATH)

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 Derive env snapshot skip from the actual root

When callers override manifest.root to a parent of $HOME (for example /home or /), the env file written at $HOME/.openai_agents_env is still inside the persisted workspace, but this registers only ./.openai_agents_env as the tar exclusion. persist_workspace() tars from manifest.root, so snapshots in that configuration will include user/.openai_agents_env (or home/user/.openai_agents_env) with the exported secret values; compute the skip path relative to the actual workspace root or place the secret file outside it.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 7e64cf03. The skip path is now computed relative to the actual manifest.root: _inject_user_env takes the env file's absolute path ($HOME/.openai_agents_env, where $HOME is AgentRun's /home/user) and registers env_file_abs.relative_to(manifest.root) — so root /home excludes user/.openai_agents_env, root / excludes home/user/.openai_agents_env, and a root that doesn't contain $HOME registers nothing (no leak, no error). Added test_inject_user_env_skip_path_relative_to_ancestor_root and test_inject_user_env_skip_noop_when_env_outside_root.

sandbox,
normalized[0],
normalized[1:],
cwd=self.state.manifest.root,

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 Honor default_cwd for user execs

For sessions created with AliyunSandboxClientOptions(default_cwd=...), user exec calls never use it because _exec_internal always passes manifest.root into _run_command; the fallback to state.default_cwd in _run_command only runs when cwd is None. As a result, exec('pwd') and tool shell commands run in the workspace root instead of the requested default cwd; omit cwd here or pass the configured value.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 7e64cf03. _exec_internal now passes cwd=self.state.default_cwd or self.state.manifest.root, so user execs honor a configured default_cwd and fall back to the workspace root only when it's unset. Added test_exec_uses_default_cwd and test_exec_defaults_to_manifest_root_without_default_cwd.

@iridescentWen iridescentWen force-pushed the feat/aliyun-sandbox branch from dc63eaa to 7e64cf0 Compare July 6, 2026 03:14
@iridescentWen

Copy link
Copy Markdown
Author

@codex review

Addressed both findings from the last pass in 7e64cf03:

  • Snapshot skip (P1): the exclusion is now computed relative to the actual manifest.root, covering roots that are ancestors of $HOME (e.g. /home, /).
  • default_cwd (P2): _exec_internal now honors a configured default_cwd for user execs.

New tests added; make format/lint/typecheck/tests all pass.

@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: 7e64cf0309

ℹ️ 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".

sandbox,
normalized[0],
normalized[1:],
cwd=self.state.default_cwd or self.state.manifest.root,

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 Run internal execs from the workspace root

When default_cwd is set to a subdirectory, this cwd is now applied to every exec, including SDK-internal calls. For example, persist_workspace() runs tar cf ... . through self.exec(..., shell=False), so with default_cwd=/home/user/project the snapshot archives only that subdirectory instead of the manifest root, dropping sibling workspace files on resume; the same issue can make runtime-helper installation fail before a manifest-created default cwd exists. Internal commands should either force manifest.root// or the default cwd should only apply to user execs.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 11b8cbae. The prior pass applied default_cwd inside _exec_internal, but every internal operation (runtime-helper install, workspace setup, persist_workspace/hydrate_workspace, filesystem ops) funnels through that same path — so it broke far more than persist. Since the base session offers no user-vs-internal exec distinction, and default_cwd was already vestigial (never applied to execs originally, unused in examples, no other backend has it), I removed the default_cwd option entirely: all execs now run from the workspace root. This resolves this finding and the previous 'honor default_cwd' one together without a half-working option. Updated the exec test to test_exec_runs_in_manifest_root.

# CodeInterpreterSandbox context manager polls health for this reason.
# We bypass that context manager, so wait explicitly before the first
# exec (e.g. the workspace mkdir) can hit a not-yet-ready sandbox.
await self._wait_until_healthy(sandbox)

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 Clean up sandboxes that fail health checks

If the health wait below times out or raises, the remote sandbox has already been created and stored on self._sandbox, but AliyunSandboxClient.create() propagates the exception without returning a session. Callers therefore have no handle to delete, so each boot/health timeout leaks an AgentRun sandbox until the idle timeout; wrap the health wait with best-effort deletion or defer publishing the handle until after it is healthy.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 11b8cbae. _ensure_sandbox now wraps the health wait: if _wait_until_healthy times out or raises, it calls _stop_attached_sandbox() (best-effort delete_sandbox_async) before re-raising, so a sandbox that never becomes healthy is deleted instead of leaking until its idle timeout. Extended test_client_create_waits_for_health_timeout to assert the sandbox is deleted.

@iridescentWen iridescentWen force-pushed the feat/aliyun-sandbox branch from 7e64cf0 to 11b8cba Compare July 6, 2026 03:36
@iridescentWen

Copy link
Copy Markdown
Author

@codex review

Addressed both findings from the last pass in 11b8cbae:

  • Internal exec cwd (P1): removed the vestigial default_cwd option; all execs now run from the workspace root, so internal ops (persist/hydrate, runtime helpers, workspace setup) are no longer redirected. This also resolves the earlier 'honor default_cwd' P2, since a user/internal exec distinction isn't available and the option was never functional.
  • Health-timeout cleanup (P2): _ensure_sandbox now deletes the sandbox if it never becomes healthy, instead of leaking it.

New/updated tests; make format/lint/typecheck/tests all pass.

@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: 11b8cbae39

ℹ️ 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 +1122 to +1125
sandbox = CodeInterpreterSandbox.model_validate(base_sandbox.model_dump(by_alias=False))
sandbox._config = config
logger.info("AliyunSandboxClient reattached sandbox: sandbox_id=%s", sandbox_id)
return sandbox, client

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 Fall back when reattached sandboxes are unhealthy

When get_sandbox_async() returns a sandbox record for an expired/stopped instance rather than raising 404, resume marks it as preserved and _ensure_sandbox() will keep reusing this handle; the next start() then fails during the workspace probe or mkdir instead of provisioning a fresh sandbox and restoring the snapshot. Please health-check the reattached CodeInterpreterSandbox here and return (None, None) or raise on not-ready states so the existing fresh-sandbox fallback can run.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in cc22ab9b. _reattach_sandbox now health-checks the reattached sandbox: after get_sandbox_async, it calls check_health_async and returns (None, None) if the instance isn't ready (or the probe fails), so resume falls through to provisioning a fresh sandbox + snapshot restore instead of reusing an expired/stopped handle. Added test_client_resume_reattach_unhealthy_falls_back.

)
tar_command = ("tar", "xf", archive_path.as_posix(), "-C", root.as_posix())
try:
self._validate_tar_bytes(raw_bytes)

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 external symlinks before remote untar

For snapshot/archive data containing a symlink whose target is outside the workspace, _validate_tar_bytes() currently uses the default allow_external_symlink_targets=True and then hands the archive to remote tar xf, so the restored workspace can contain links to paths outside manifest.root that user shell commands can follow. Other hosted backends disable external symlink targets before remote extraction; please pass allow_external_symlink_targets=False here as well.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in cc22ab9b. _validate_tar_bytes now passes allow_external_symlink_targets=False to validate_tarfile, matching the other hosted backends, so an archive with a symlink pointing outside manifest.root is rejected before the remote tar xf. Added test_validate_tar_bytes_external_symlink_rejected (verified it passes with the old default True and is rejected with False).

@iridescentWen iridescentWen force-pushed the feat/aliyun-sandbox branch from 11b8cba to cc22ab9 Compare July 6, 2026 03:46
@iridescentWen

Copy link
Copy Markdown
Author

@codex review

Addressed both findings from the last pass in cc22ab9b:

  • Reattach health (P1): _reattach_sandbox now health-checks the sandbox returned by get_sandbox_async and falls back to a fresh sandbox when it's not ready.
  • External symlinks (P2): _validate_tar_bytes now passes allow_external_symlink_targets=False, matching the other hosted backends, so external-symlink archives are rejected before remote extraction.

New tests added; make format/lint/typecheck/tests all pass.

@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: cc22ab9b95

ℹ️ 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 +600 to +602
f"cat > {_AGENTS_ENV_FILE} <<'OPENAI_AGENTS_ENV_EOF'\n"
f"{env_lines}\n"
"OPENAI_AGENTS_ENV_EOF\n"

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 Avoid interpolating env values into a fixed here-doc

When an env value contains a newline with OPENAI_AGENTS_ENV_EOF on its own line, this fixed here-doc is terminated early and the remaining bytes are executed by the setup sh -c during start(). Since _prepare_backend_workspace merges values from AliyunSandboxClientOptions.env and manifest.environment, any caller that passes user-controlled data as an env value can turn that data into arbitrary sandbox commands, potentially before other secret env lines are protected; write the env file via the file API/base64 or otherwise avoid a delimiter that can appear verbatim in the payload.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 5eb654e5. _inject_user_env no longer uses a here-doc: the env file is now transported as printf %s '<base64>' | base64 -d > $FILE. base64 output contains no shell-significant characters (and no newlines), so an env value can't smuggle a here-doc terminator or any shell command into the setup script — the whole payload stays opaque until it's decoded into the (chmod 600) file. Keys are still validated, values are still shlex.quoted for when the file is later sourced, and the command remains redact_command=True. Added test_inject_user_env_value_with_heredoc_delimiter_is_safe (asserts the injected OPENAI_AGENTS_ENV_EOF / touch /pwned never appear as literal shell, but are preserved verbatim in the decoded file).

@iridescentWen iridescentWen force-pushed the feat/aliyun-sandbox branch from cc22ab9 to 5eb654e Compare July 6, 2026 04:00
@iridescentWen

Copy link
Copy Markdown
Author

@codex review

Addressed the here-doc injection finding in 5eb654e5: _inject_user_env now transports the env file as base64 (printf %s '<b64>' | base64 -d > $FILE) instead of a here-doc, so env values can't terminate a delimiter early or inject commands during start. New test added; make format/lint/typecheck/tests all pass.

@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: 5eb654e5b4

ℹ️ 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".

if timeout_s is None:
timeout_int = ALIYUN_PROCESS_CMD_HARD_CAP_S
else:
timeout_int = max(1, min(int(timeout_s), ALIYUN_PROCESS_CMD_HARD_CAP_S))

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 Respect exec timeouts above the AgentRun cmd cap

When callers pass exec(..., timeout=60) or any value above the 30s /processes/cmd cap, this clamps the provider request to 30 while the outer wait_for still waits for the original timeout. A command that is valid for the caller's timeout but runs longer than 30s will be killed/reported as timed out early; route those calls through the long-running execution path or reject them explicitly instead of silently shortening the timeout.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 76c1d207. Confirmed against the SDK: ProcessOperations.cmd_async(..., timeout=30) just forwards the timeout (default 30, no enforced ceiling) — the 30s cap was a deployment-specific assumption. _run_command no longer clamps: it forwards the caller's timeout as-is (defaulting to 30 when unset), matching the outer wait_for, so a command the caller allowed more time for isn't killed early. If a specific gateway enforces a shorter limit it surfaces as a provider error rather than a silent cap. Renamed the misleading ALIYUN_PROCESS_CMD_HARD_CAP_SDEFAULT_ALIYUN_EXEC_TIMEOUT_S; updated tests (test_run_command_forwards_caller_timeout, test_run_command_defaults_timeout_when_unset).

Comment on lines +377 to +379
export OPENAI_API_KEY=...
export ALIBABA_CLOUD_ACCESS_KEY_ID=...
export ALIBABA_CLOUD_ACCESS_KEY_SECRET=...

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 Require an AgentRun account id in the Aliyun setup

Following the documented setup with only ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET passes the runner's credential check, but the AgentRun SDK still needs an account id (or an explicit data endpoint) to construct the data-plane endpoint before sandbox creation. Users who copy these required env vars will fail later with a configuration error; include AGENTRUN_ACCOUNT_ID/equivalent here and enforce it in the runner unless --api-key/data-endpoint auth is being used.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 76c1d207. Confirmed the SDK's Config.get_data_endpoint() builds https://{account_id}.agentrun-data.{region}.aliyuncs.com and get_account_id() raises account id is not set when it's missing and no data endpoint is set. Updated the README setup to export AGENTRUN_ACCOUNT_ID (or FC_ACCOUNT_ID / AGENTRUN_DATA_ENDPOINT, or --account-id), and _require_aliyun_credentials in the runner now enforces an account id (or data endpoint) up front with a clear message instead of letting users fail later at sandbox creation.

@iridescentWen iridescentWen force-pushed the feat/aliyun-sandbox branch from 5eb654e to 76c1d20 Compare July 6, 2026 04:12
@iridescentWen

Copy link
Copy Markdown
Author

@codex review

Addressed both findings from the last pass in 76c1d207:

  • Exec timeout (P2): _run_command no longer clamps to 30s; it forwards the caller's timeout as-is (SDK's cmd_async doesn't enforce a ceiling), consistent with the outer wait_for.
  • Account id (P2): README now documents AGENTRUN_ACCOUNT_ID (required for the data-plane endpoint) and the runner enforces it up front.

New/updated tests; make format/lint/typecheck/tests all pass.

@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: 76c1d20715

ℹ️ 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 +1086 to +1090
config = _build_config(
access_key_id=self._access_key_id,
access_key_secret=self._access_key_secret,
account_id=self._account_id,
api_key=self._api_key,

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 Preserve option credentials across resume

create() accepts access_key_id, access_key_secret, account_id, and api_key on AliyunSandboxClientOptions, but those values are only used to build the initial Config and are omitted from serialized state. When a caller creates a session with option-only credentials and then calls resume() on the same client/state, this block rebuilds Config from the empty client fields, so reattach/fresh provisioning loses the required account/keys and fails. Cache the resolved option credentials on the client or require them at client construction instead.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in edadaa86. create() now caches the resolved option-over-client credentials back onto the client (_access_key_id etc.), so a create()-then-resume() flow on the same client keeps working when credentials were supplied only via AliyunSandboxClientOptions. Serialized state still never carries secrets — resume rebuilds Config from the client fields, which are now populated. Added test_client_resume_uses_option_credentials_from_create.

if timeout_s is None:
timeout_int = DEFAULT_ALIYUN_EXEC_TIMEOUT_S
else:
timeout_int = max(1, int(timeout_s))

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 Avoid truncating fractional exec timeouts

exec(timeout=...) accepts floats and the outer asyncio.wait_for uses the original value, but this floors the provider timeout before sending it to AgentRun. For a caller budget like 1.9, the remote command receives 1 second and can be killed before the outer timeout, which silently shortens the requested budget. Forward the float unchanged or round up if the SDK requires an int.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in edadaa86. _run_command now uses math.ceil(timeout_s) instead of int(...), so a fractional budget like 1.9 forwards as 2 (not 1) and the provider can't kill the command before the outer wait_for. Added test_run_command_rounds_up_fractional_timeout.

Comment on lines +98 to +101
has_keys = bool(access_key_id and access_key_secret) or bool(
env.get("ALIBABA_CLOUD_ACCESS_KEY_ID") and env.get("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
)
if not (has_keys or (account_id and api_key)):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge Allow SDK credential providers in the runner

The README added for this runner says agentrun-sdk can resolve credentials from Alibaba config files or RAM roles, but this preflight only accepts CLI access keys, ALIBABA_CLOUD_ACCESS_KEY_*, or account_id+api_key. In environments that rely on those supported SDK credential providers with AGENTRUN_ACCOUNT_ID set, the example exits before the SDK can resolve them; limit the preflight to the account-id requirement or let Config validate credentials.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in edadaa86. The runner preflight now only enforces the account-id requirement (the one hard prerequisite for the data-plane endpoint) and leaves the access credentials to the SDK, which resolves them from CLI flags, env vars, an Alibaba Cloud config file, or a RAM role — matching what the README describes. It no longer exits early when a supported SDK credential provider is in use with AGENTRUN_ACCOUNT_ID set.

@iridescentWen iridescentWen force-pushed the feat/aliyun-sandbox branch from 76c1d20 to edadaa8 Compare July 6, 2026 04:27
@iridescentWen

Copy link
Copy Markdown
Author

@codex review

Addressed all three findings from the last pass in edadaa86:

  • Resume credentials (P2): create() caches resolved option credentials on the client so resume() works with option-only creds (state still carries no secrets).
  • Fractional timeout (P2): _run_command rounds the timeout up (math.ceil) instead of truncating.
  • Runner preflight (P3): now only requires the account id and lets the SDK resolve access credentials from its providers.

New tests added; make format/lint/typecheck/tests all pass.

@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: edadaa86ff

ℹ️ 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 +1065 to +1069
self._access_key_id = self._resolve_credential(options.access_key_id, self._access_key_id)
self._access_key_secret = self._resolve_credential(
options.access_key_secret, self._access_key_secret
)
self._account_id = self._resolve_credential(options.account_id, self._account_id)

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 Keep option credentials scoped to one session

When the same AliyunSandboxClient is reused for more than one tenant/account and credentials are supplied through per-call AliyunSandboxClientOptions, each create() mutates the client-wide credentials. A later resume() of an earlier session rebuilds its Config from whatever credentials the most recent create stored, so it can reattach or provision the resumed workspace under the wrong account (and can also mix an old AK/SK with a newer account id). Keep option credentials scoped to the session instead of overwriting the shared client state.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 2c9329ad — and this reverts the client-caching from my previous reply, which you correctly flagged as unsafe. create() no longer writes resolved credentials back onto the client; the option-over-client resolution is used only to build THIS session's Config (scoped to the session). So a client reused across tenants via per-call options no longer has its shared credentials mutated, and a later resume() of an earlier session can't run under the wrong account. Trade-off: resume() relies on client-level credentials (or the SDK's env/credential providers), since per-call option secrets can't be safely persisted (state carries no secrets by design). Added test_create_does_not_mutate_client_credentials.

Comment on lines +512 to +515
result = await sandbox.process.cmd_async(
command=shell_cmd,
cwd=effective_cwd,
timeout=timeout_int,

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 Apply configured env to direct exec commands

When a manifest/options env is configured, _inject_user_env only makes it available via ~/.profile, but this path sends shell=False commands directly to AgentRun as the raw command string. Calls such as session.exec("python", ..., shell=False) and SDK helpers that use shell=False therefore never source the profile and run without the configured environment, unlike the other sandbox backends. Please source the env file or otherwise inject the env for the direct-exec path too.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 2c9329ad. Once the env file has been written, _run_command prepends . $HOME/.openai_agents_env 2>/dev/null; to the command it sends to AgentRun (whose cmd API has no env parameter). That gives shell=False direct execs — and SDK helpers that use shell=False — the configured environment too, not just the sh -lc login-shell path that reads .profile. Sessions without configured env are unaffected (no prefix). Added test_exec_shell_false_sources_env_after_injection and test_exec_without_env_has_no_source_prefix.

@iridescentWen iridescentWen force-pushed the feat/aliyun-sandbox branch from edadaa8 to 2c9329a Compare July 6, 2026 04:42
@iridescentWen

Copy link
Copy Markdown
Author

@codex review

Addressed both findings from the last pass in 2c9329ad:

  • Credential scoping (P2): reverted the client-credential caching (it was unsafe for multi-tenant reuse); option credentials are now scoped to the session's Config only, and create() no longer mutates shared client state.
  • Env for direct execs (P2): once the env file exists, _run_command sources it inline so shell=False execs also get the configured environment.

New tests added; make format/lint/typecheck/tests all pass.

@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: 2c9329ad3e

ℹ️ 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 +364 to +365
if env:
await self._inject_user_env(env)

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 Reinject env after restoring snapshots

When a fresh resume restores a snapshot under the default /home/user root, BaseSandboxSession.start() calls _prepare_backend_workspace() before _start_workspace(), so this env injection runs before restore_snapshot_into_workspace_on_resume() clears the workspace. Because _inject_user_env() deliberately excludes .openai_agents_env from snapshots, the restore removes the just-written env file and later execs only source a missing file, so resumed sandboxes lose options.env/manifest environment variables. This breaks resumed jobs that rely on configured secrets or other env after snapshot hydration.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in $(git rev-parse --short HEAD). Env injection moved from _prepare_backend_workspace() to a new _after_start() override (extracted into _apply_configured_env()). BaseSandboxSession.start() runs _after_start() after _start_workspace(), i.e. after any snapshot restore — so the env file (which is excluded from snapshots) is written after the restore instead of being wiped by it. This covers both fresh create and resume. Tests renamed to test_after_start_injects_env / test_after_start_merges_manifest_env.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

(correction: the commit is e1aad243 — my previous reply mis-rendered the SHA.)

@iridescentWen iridescentWen force-pushed the feat/aliyun-sandbox branch from 2c9329a to e1aad24 Compare July 6, 2026 04:51
@iridescentWen

Copy link
Copy Markdown
Author

@codex review

Addressed the snapshot/env-ordering finding in e1aad243: env injection moved to a _after_start() override (runs after _start_workspace()/snapshot restore), so the env file — excluded from snapshots — is written after the restore instead of being wiped by it, on both fresh create and resume. New tests; make format/lint/typecheck/tests all pass.

@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: e1aad2434d

ℹ️ 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".

# deliberately excluded from snapshots, so injecting it before the restore
# would let the restore wipe it and leave resumed execs sourcing a missing
# file. Running after start covers both fresh create and resume.
await self._apply_configured_env()

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 Inject env before applying manifest entries

Because this hook runs only after _start_workspace() has finished, the Aliyun session does not source options.env or manifest.environment while the initial manifest is being applied. Manifest entries that execute commands during start() and rely on those env vars—for example generic S3/Rclone or mount-s3 mounts using env-based credentials—will fail on Aliyun even though the same manifest works on other hosted backends that inject env at sandbox creation or per exec. Consider making the env available before manifest/ephemeral entry application as well as reinjecting it after snapshot restore.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in d2de6c01 by replacing the env mechanism entirely, per maintainer guidance to follow how the other backends handle env. Instead of writing a ~/.openai_agents_env file and sourcing it from .profile, the resolved environment (options + manifest, manifest wins, unsafe names dropped, values shlex.quoted) is now inlined as a KEY=val ... prefix on every command — the same per-exec model as e2b/daytona/runloop, adapted to AgentRun (whose cmd API has no env parameter). Because env travels inline with each command and touches no workspace file, this resolves this finding and the whole class it belonged to at once: it's available during manifest/mount application, survives snapshot restore, needs no login shell (works for shell=False), and never lands in snapshots or logs. Net: the here-doc/base64/.profile/snapshot-skip machinery (and its edge cases) is gone. New env tests cover the prefix, manifest/option merge, unsafe-name skipping, metachar quoting, shell=False, and log redaction.

@iridescentWen iridescentWen force-pushed the feat/aliyun-sandbox branch from e1aad24 to d2de6c0 Compare July 6, 2026 05:10
@iridescentWen

Copy link
Copy Markdown
Author

@codex review

Reworked env handling in d2de6c01 per maintainer guidance to mirror the other backends. AgentRun's cmd API has no env parameter, so — like e2b/daytona/runloop inject env per exec — the resolved environment is now inlined as a KEY=val ... prefix on every command instead of a .profile-sourced file. This removes the file/here-doc/snapshot machinery and resolves the whole env edge-case class at once (available during manifest application, survives snapshot restore, works for shell=False, never in snapshots or logs). New tests added; make format/lint/typecheck/tests all pass.

@iridescentWen

Copy link
Copy Markdown
Author

Closing in favor of a fresh PR to reset the review thread (this one accumulated many review rounds). The same, finalized commit will be re-submitted as a new PR.

@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: d2de6c01ee

ℹ️ 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 +538 to +540
if _is_retryable_gateway(exc) and attempt < max_attempts:
backoff_s = ALIYUN_GATEWAY_502_BACKOFF_BASE_S * (2 ** (attempt - 1))
logger.warning(

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 Avoid retrying side-effecting exec commands

When AgentRun returns a transient 502 after it has accepted /processes/cmd, this branch retries the exact same command for every exec, including user/tool commands. For non-idempotent commands such as appending to files, package installs, or external API calls, a lost response can make the SDK run the side effect multiple times and corrupt the workspace or external state; keep automatic retries to known-idempotent internal probes or let callers retry explicitly.

Useful? React with 👍 / 👎.

Comment on lines +508 to +514
logger.info(
"AliyunSandboxSession._run_command sandbox_id=%s cwd=%s timeout=%ss cmd=%r",
self.state.sandbox_id,
effective_cwd,
timeout_int,
shell_cmd[:500],
)

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 Stop logging raw exec arguments

This logs every command string at INFO, so production applications that pass secrets in argv or shell snippets (for example curl -H 'Authorization: Bearer ...' or signed URLs) will write those values to normal application logs even though the env prefix is redacted. Consider dropping command payloads to DEBUG or redacting/truncating them behind an explicit opt-in.

Useful? React with 👍 / 👎.

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.

1 participant