cloud agent: product-owned Daytona snapshot + two fixes found by running agents inside a Computer - #5745
cloud agent: product-owned Daytona snapshot + two fixes found by running agents inside a Computer#5745Hmbown wants to merge 9 commits into
Conversation
…finition Pins the released v0.9.11 Linux x64 engine (commit 96d13a0, sha256 c02969556e51e138afa3fe9c97a1359878cd3d1986b1ce1f5fa96c93c6909416) into a debian:bookworm-slim Computer image with the agent toolchain (git, node 22, python3, ripgrep, build-essential), non-root agent user, and /workspace prepared for the cloud dispatcher (PRD 4.5). Built and probed on Daytona as snapshot codewhale-cloud-agent (4 CPU / 8 GB / 10 GB). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014aDEyM2a4pPZ9qqMDrP5YX Signed-off-by: CodeWhale Bot <bot@codewhale.net>
…ntinue The `codewhale` dispatcher rejects `--provider`/`--model` after `exec` and forwards the top-level flags as CODEWHALE_PROVIDER / CODEWHALE_MODEL. `resolve_exec_resume_route` only looked at the exec-level flags, so `codewhale --provider modelstudio-token-plan --model qwen3.8-flash exec --continue ...` silently restored the saved route (observed in the Daytona cloud-agent e2e on 2026-08-30: the resumed turn ran on deepseek-v4-pro). Fold the launch overrides into the explicit route signals for resume; a fresh session already treated them as explicit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014aDEyM2a4pPZ9qqMDrP5YX Signed-off-by: CodeWhale Bot <bot@codewhale.net>
A completed worker hydrated from a previous session's workspace ledger is invisible to status/peek/followup (`Agent not found in the active session`), yet spawn still refused its name with "already in use by agent_id ... (status: completed)". A second `codewhale exec` in the same workspace could not spawn worker-a/worker-b again and burned six turns recovering (Daytona cloud-agent e2e, 2026-08-30). Skip prior-session agents in the collision check, matching the other model-facing actions. The existing duplicate-name test now stamps the manager's own boot id so it keeps covering the live-session case. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014aDEyM2a4pPZ9qqMDrP5YX Signed-off-by: CodeWhale Bot <bot@codewhale.net>
…create key injection Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Claude finished @Hmbown's task in 2m 47s —— View job Claude finished the reviewTodo list
SummaryTwo focused runtime fixes plus a product-owned Daytona snapshot definition. The two code fixes are correct, minimal, and reuse existing helpers rather than adding new ones. One real issue found in the new README. Findings1. The "Credential exposure note" (lines 44-54) is explicit: "Do NOT put provider keys in create-time env. Inject them after the Computer starts" — because Daytona persists The very next paragraph (lines 56-58) says the opposite: "an end-to-end run must also inject provider env at create time, never on a command line." As written, a reader following the second instruction would recreate the exact leak the first paragraph just warned about. This should say "inject post-create" (matching the note above and the Correctness —
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c6051a38c5
ℹ️ 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".
| The dispatcher injects only `CODEWHALE_API_KEY` (account machine token). The | ||
| engine resolves model-provider keys from ambient env, so an end-to-end run | ||
| must also inject provider env at create time, never on a command line: |
There was a problem hiding this comment.
Remove the create-time provider-key instruction
When an operator follows this end-to-end setup, this line tells them to inject MODELSTUDIO_API_KEY or DEEPSEEK_API_KEY through Daytona's create-time environment, even though the immediately preceding warning establishes that these values persist server-side and are retrievable by anyone with Daytona API access. Following the documented procedure therefore exposes provider credentials; it should consistently describe a usable post-create secret-injection path instead.
Useful? React with 👍 / 👎.
| if let Some(existing) = self.agents.values().find(|existing| { | ||
| existing.session_name == name && !self.is_from_prior_session(existing) |
There was a problem hiding this comment.
Scope duplicate names by conversation rather than process boot
When Codewhale restarts and resumes the same saved conversation, load_state retains each agent's persisted owner_session_id while the manager gets a new boot ID. This predicate consequently ignores an archived agent belonging to the resumed conversation and admits another child with the same name; resolve_agent_ref_for_session then finds both rows by that shared owner and makes name-based status/peek/followup ambiguous. Conversely, switching conversations within one process still lets a same-boot foreign row reserve the name. The collision check should use the active runtime.context.state_namespace against owner_session_id, which is the identity used by those user-facing lookups.
Useful? React with 👍 / 👎.
| - Snapshot name: `codewhale-cloud-agent` (`DEFAULT_CLOUD_AGENT_SNAPSHOT`); | ||
| override with `CODEWHALE_DISPATCH_SNAPSHOT`. | ||
| - The runner clones the repository into `/workspace` (`SANDBOX_WORKSPACE`) | ||
| and runs `codewhale exec --auto "<prompt>"` there through the Daytona | ||
| toolbox `process/execute` endpoint. |
There was a problem hiding this comment.
Wire the new snapshot into the production dispatcher
In the reviewed tree this advertised contract has no implementation: a repository-wide symbol search finds none of DEFAULT_CLOUD_AGENT_SNAPSHOT, CODEWHALE_DISPATCH_SNAPSHOT, or SANDBOX_WORKSPACE outside this new documentation, and LiveDaytonaLauncher::create_sandbox in crates/tui/src/cloud_dispatch.rs:663-688 posts only a generated name and labels. It neither selects this snapshot nor clones the remote or invokes process/execute, so /dispatch continues creating an unspecified idle sandbox and the newly added image is never used by the product flow.
Useful? React with 👍 / 👎.
| # ---- pins (release v0.9.11, tag commit 96d13a0bc3f40280ea3865280ad5ccf0e2845e6f) | ||
| ENV CODEWHALE_VERSION=0.9.11 \ | ||
| CODEWHALE_COMMIT=96d13a0bc3f40280ea3865280ad5ccf0e2845e6f \ | ||
| CODEWHALE_ASSET_URL=https://github.com/Hmbown/CodeWhale/releases/download/v0.9.11/codewhale-linux-x64 \ | ||
| CODEWHALE_ASSET_SHA256=c02969556e51e138afa3fe9c97a1359878cd3d1986b1ce1f5fa96c93c6909416 \ |
There was a problem hiding this comment.
Ship a binary containing the accompanying cloud fixes
When this snapshot is built and used, it always installs commit 96d13a0…, which is an ancestor of the reviewed commit and does not contain either exec_resume_route_overrides or the changed subagent name-reservation logic added here. Consequently the advertised cloud image still exhibits the wrong-provider resume and stale-name failures that this commit claims to fix, even after rebuilding from this Dockerfile. Pin a release containing these changes or build and checksum the reviewed source revision.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Codewhale review
Adds a product-owned Daytona cloud-agent snapshot and fixes two issues found by running agents inside a Computer: dispatcher-forwarded --provider/--model are now honored on exec --resume/--continue, and subagent session-name reservation is scoped to the live session. The Rust changes include focused unit tests, and the Dockerfile/README are generally well structured. Main concerns are documentation contradictions around credential injection and a couple of test-correctness/integration gaps.
Findings
- [WARNING] Dockerfile comment still says provider keys arrive via Daytona create-time env injection (
computer/snapshots/cloud-agent/Dockerfile:8)
The header comment claims provider keys and the account machine token arrive only through sandbox create-time env injection, but the README's credential-exposure note states create-time env is persisted server-side and readable via GET /sandbox/{id}; provider keys must be injected post-create from stdin into a 0600 file. Update this comment to avoid leading users to inject provider keys at create time. - [WARNING] README instructs provider env injection at create time, contradicting its own security guidance (
computer/snapshots/cloud-agent/README.md:58)
The paragraph after the credential-exposure note says an end-to-end run 'must also inject provider env at create time', while the preceding note says 'Do NOT put provider keys in create-time env' and requires post-create injection. This ambiguity can cause a Daytona API user to expose provider credentials. Clarify that 'create time' refers to the toolbox process/execute environment, not daytona create envVars, or change the wording. - [WARNING] Launch provider override is reduced to a bool and lacks an integration test for the resumed dispatch path (
crates/tui/src/lib.rs:997)
exec_resume_route_overrides returns only (explicit_provider: bool, explicit_model: Option), so the actual provider value is not passed to resolve_exec_resume_route. The caller depends on config.provider already being mutated from the launch env before this code. The new unit tests exercise only the helper, not the real path where CODEWHALE_PROVIDER/MODEL are set and a saved session is resumed, so a regression in that side effect would keep silently restoring the saved route. Add an integration test or pass the resolved provider value through. - [INFO] Subagent regression test passes on any non-collision failure (
crates/tui/src/tools/subagent/tests.rs:8842)
In spawn_session_name_held_by_prior_session_agent_does_not_collide, the Err arm asserts only that the error message does not contain 'already in use'. If spawn_background_with_assignment_options fails for an unrelated reason such as setup or tool context, the test still passes and does not actually verify the session-name scoping fix. Prefer panicking on Err or asserting the Ok path with an explicit expect.
Assessment
The core code changes are sound and well-targeted, but the documentation has contradictory guidance on provider key injection that should be reconciled before merge. The subagent test should be tightened so unexpected errors do not cause a false pass, and the resumed dispatch path would benefit from an integration-level test proving the forwarded launch overrides are actually applied.
Advisory review by Codewhale (codewhale review --pr 5745 --post, head c6051a38c5c286d7a51474570f895edf9a082306). Line-specific findings are also posted as inline review comments; mechanical fixes arrive as committable suggestions you can apply from the Files tab. CODEOWNERS approval still governs merge.
| # binary (static musl, no glibc floor) from the GitHub release by exact URL, | ||
| # verifies its sha256 against codewhale-artifacts-sha256.txt, and records the | ||
| # commit + digest as OCI labels (PRD 4.5: commit- and digest-pinned Linux | ||
| # binary inside the Computer). No secrets are baked in: provider keys and the |
There was a problem hiding this comment.
[WARNING] Dockerfile comment still says provider keys arrive via Daytona create-time env injection
The header comment claims provider keys and the account machine token arrive only through sandbox create-time env injection, but the README's credential-exposure note states create-time env is persisted server-side and readable via GET /sandbox/{id}; provider keys must be injected post-create from stdin into a 0600 file. Update this comment to avoid leading users to inject provider keys at create time.
|
|
||
| The dispatcher injects only `CODEWHALE_API_KEY` (account machine token). The | ||
| engine resolves model-provider keys from ambient env, so an end-to-end run | ||
| must also inject provider env at create time, never on a command line: |
There was a problem hiding this comment.
[WARNING] README instructs provider env injection at create time, contradicting its own security guidance
The paragraph after the credential-exposure note says an end-to-end run 'must also inject provider env at create time', while the preceding note says 'Do NOT put provider keys in create-time env' and requires post-create injection. This ambiguity can cause a Daytona API user to expose provider credentials. Clarify that 'create time' refers to the toolbox process/execute environment, not daytona create envVars, or change the wording.
| .map(str::to_string) | ||
| }; | ||
| let explicit_provider = | ||
| non_empty(exec_provider).is_some() || non_empty(launch_provider).is_some(); |
There was a problem hiding this comment.
[WARNING] Launch provider override is reduced to a bool and lacks an integration test for the resumed dispatch path
exec_resume_route_overrides returns only (explicit_provider: bool, explicit_model: Option), so the actual provider value is not passed to resolve_exec_resume_route. The caller depends on config.provider already being mutated from the launch env before this code. The new unit tests exercise only the helper, not the real path where CODEWHALE_PROVIDER/MODEL are set and a saved session is resumed, so a regression in that side effect would keep silently restoring the saved route. Add an integration test or pass the resolved provider value through.
| .agents | ||
| .get(&result.agent_id) | ||
| .expect("fresh agent registered"); | ||
| assert_eq!(fresh.session_name, "researcher"); |
There was a problem hiding this comment.
[INFO] Subagent regression test passes on any non-collision failure
In spawn_session_name_held_by_prior_session_agent_does_not_collide, the Err arm asserts only that the error message does not contain 'already in use'. If spawn_background_with_assignment_options fails for an unrelated reason such as setup or tool context, the test still passes and does not actually verify the session-name scoping fix. Prefer panicking on Err or asserting the Ok path with an explicit expect.
…al contract - Dockerfile/README: Daytona create-time env is server-visible; provider secrets must never be injected at create time. Post-create delivery over an execution channel (stdin, never argv) into a 0600 file, removed at teardown, is the only documented path. The provider env table is reframed as reference for a future supported bridge, and api_key_env is correctly described as holding an env-var name, not a file path. - Image/probe receipts re-labeled as manual image evidence, not launch proof; the snapshot remains an image definition, not a wired dispatcher. - tui: test-only strengthening — exec_resume route override covered end-to-end through Config::load with CODEWHALE_PROVIDER/CODEWHALE_MODEL; session-name reservation test now hard-expects same-name respawn. Local proof (RUST_MIN_STACK=33554432, --locked): subagent 676/676, exec_resume 4/4, session_name 7/7; cargo fmt --check clean. Signed-off-by: CodeWhale Bot <bot@codewhale.net>
| kind = "openai-compatible" | ||
| base_url = "https://launch.example.test/v1" | ||
| model = "configured-launch-model" | ||
| api_key = "test-only-key" |
There was a problem hiding this comment.
Blocker This changed line looks like a hardcoded secret.
|
Claude finished @Hmbown's task in 2m 32s —— View job Claude finished @Hmbown's task in 5m — (branch
|
There was a problem hiding this comment.
Codewhale review
PR adds a product-owned Daytona snapshot definition for the codewhale-cloud-agent Computer, fixes exec resume to honor dispatcher-forwarded provider/model overrides, and scopes subagent session-name reservation to the live session. The Rust fixes include focused unit tests. The main gap is product-level: the snapshot is not wired into the dispatcher and the required post-create credential injection bridge is not implemented.
Findings
- [WARNING] Cloud agent snapshot is not connected to any runtime path (
computer/snapshots/cloud-agent/Dockerfile:1)
The new Dockerfile defines a codewhale-cloud-agent snapshot, but crates/tui/src/cloud_dispatch.rs still only creates a named/labelled Daytona sandbox and does not select this snapshot, clone a repository, inject sandbox environment, call the toolbox execution endpoint, or run codewhale. The README disclaims this, but the snapshot should not be presented as product-owned launch evidence until that wiring exists. - [WARNING] Provider credential injection remains unimplemented (
computer/snapshots/cloud-agent/README.md:49)
The PR description and README both state that credentials fail as shipped: only CODEWHALE_API_KEY is injected, and nothing consumes it as an inference-provider credential. The Dockerfile explicitly forbids create-time environment and documents a future post-create stdin bridge, but no code implements that bridge. As a result the snapshot cannot run a provider-backed Engine turn in the current product path. - [INFO] Dockerfile version assertion is brittle (
computer/snapshots/cloud-agent/Dockerfile:58)
The build hard-fails unlesscodewhale --versionemits exactlycodewhale 0.9.11 (96d13a0bc3f4). This pins the pretty version string and short hash rather than matching the known version/commit values; any release that changes formatting will break the build despite a valid binary. - [INFO] No end-to-end test for dispatcher-forwarded CLI overrides (
crates/tui/src/lib.rs:14960)
The new unit tests exercise exec_resume_route_overrides and Config::load plus resolve_exec_resume_route directly, but there is no test that a realcodewhale --provider X --model Y exec --resume ...invocation forwards the top-level flags into CODEWHALE_PROVIDER/CODEWHALE_MODEL and uses them. This leaves a gap around the actual dispatcher boundary that motivated the fix.
Assessment
The Rust changes are focused, readable, and backed by unit tests; they address real e2e failures without obvious correctness issues. The snapshot artifact is well-documented but definition-only, and the missing credential bridge remains a release blocker for any real cloud-agent use. Merge only as an explicit image definition plus code fixes, not as end-to-end launch evidence.
Advisory review by Codewhale (codewhale review --pr 5745 --post, head 52316808f157d4b394cd2b52093634c9fd0f132b). Line-specific findings are also posted as inline review comments; mechanical fixes arrive as committable suggestions you can apply from the Files tab. CODEOWNERS approval still governs merge.
| @@ -0,0 +1,99 @@ | |||
| # syntax=docker/dockerfile:1 | |||
There was a problem hiding this comment.
[WARNING] Cloud agent snapshot is not connected to any runtime path
The new Dockerfile defines a codewhale-cloud-agent snapshot, but crates/tui/src/cloud_dispatch.rs still only creates a named/labelled Daytona sandbox and does not select this snapshot, clone a repository, inject sandbox environment, call the toolbox execution endpoint, or run codewhale. The README disclaims this, but the snapshot should not be presented as product-owned launch evidence until that wiring exists.
| image/operator step observed; none is launch proof for dispatcher, entitlement, | ||
| credential custody, Engine execution, lifecycle, metering, or customer use. | ||
|
|
||
| ## Provider credentials inside the Computer |
There was a problem hiding this comment.
[WARNING] Provider credential injection remains unimplemented
The PR description and README both state that credentials fail as shipped: only CODEWHALE_API_KEY is injected, and nothing consumes it as an inference-provider credential. The Dockerfile explicitly forbids create-time environment and documents a future post-create stdin bridge, but no code implements that bridge. As a result the snapshot cannot run a provider-backed Engine turn in the current product path.
| && apt-get install -y --no-install-recommends nodejs \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
|
||
| # Download the released binary by exact URL and refuse anything whose sha256 |
There was a problem hiding this comment.
[INFO] Dockerfile version assertion is brittle
The build hard-fails unless codewhale --version emits exactly codewhale 0.9.11 (96d13a0bc3f4). This pins the pretty version string and short hash rather than matching the known version/commit values; any release that changes formatting will break the build despite a valid binary.
| assert_eq!(missing.provider, before); | ||
| } | ||
|
|
||
| #[test] |
There was a problem hiding this comment.
[INFO] No end-to-end test for dispatcher-forwarded CLI overrides
The new unit tests exercise exec_resume_route_overrides and Config::load plus resolve_exec_resume_route directly, but there is no test that a real codewhale --provider X --model Y exec --resume ... invocation forwards the top-level flags into CODEWHALE_PROVIDER/CODEWHALE_MODEL and uses them. This leaves a gap around the actual dispatcher boundary that motivated the fix.
…0260830 Signed-off-by: CodeWhale Bot <bot@codewhale.net>
…0260830 Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Safety-gate hermetic tests failed on failed_state_writes_never_become_in_memory_authority_and_exact_retry_reopens with WouldBlock after drop-then-reopen of the same account session (#5735). Unlock on Drop, and retry only WouldBlock, so a just-closed fd is not reported as another owner. A lock that stays held is still ownership. Local proof (hermetic HOME, RUST_MIN_STACK=8388608, --locked): runtime_chat_relay 16/16; Safety-gate tui filter 344/344; execpolicy 108/108 plus authorization_order 1/1; PR exec_resume/session-name 4/4. Signed-off-by: CodeWhale Bot <bot@codewhale.net>
| kind = "openai-compatible" | ||
| base_url = "https://launch.example.test/v1" | ||
| model = "configured-launch-model" | ||
| api_key = "test-only-key" |
There was a problem hiding this comment.
Blocker This changed line looks like a hardcoded secret.
|
Claude finished @Hmbown's task in 2m 26s —— View job Claude finished @hunter Bown's task — code review (branch
|
| kind = "openai-compatible" | ||
| base_url = "https://launch.example.test/v1" | ||
| model = "configured-launch-model" | ||
| api_key = "test-only-key" |
There was a problem hiding this comment.
Blocker This changed line looks like a hardcoded secret.
|
Claude finished @Hmbown's task in 1m 30s —— View job Claude finished @hunter Bown's task — code review (branch
|
What
Receipts and fixes from the first end-to-end run of the Codewhale Engine inside a Daytona Computer (PRD §4.5), with real turns on Alibaba Model Studio (DeepSeek V4 Pro, Qwen 3.8 Flash).
computer/snapshots/cloud-agent/— product-owned definition of thecodewhale-cloud-agentsnapshot feat(cli): cloud-dispatch remote runner — sandbox to forge PR #5712 expects (v0.9.11 static musl x64 release asset, sha256-pinned, non-rootagentuser, no secrets baked in) + README incl. a credential-exposure note (Daytona persists create-time env server-side → keys must be injected post-create from stdin into a 0600 file).exec: honour dispatcher-forwarded --provider/--model on --resume/--continue—codewhale --provider X --model Y exec --continue …silently ran on the config default provider.subagent: scope session-name reservation to the live session— a secondcodewhale execin the same workspace could not spawnworker-a/worker-bbecause completed agents from earlier boots (hydrated from.codewhale/state/subagents.v1.json) still held the names.Proven inside the Computer (level 6/7, sandboxes deleted afterwards)
R1
--version+ live Model Studio model list ✅ · R2 one agent turn, DeepSeek V4 Pro: 4 turns read→edit→bash, docstring diff landed, 16 s ✅ (api.deepseek.com fallback 7 s ✅) · R3 two subagents spawned inside (spawn_depth 1, both settled) ✅ · R3b Qwen 3.8 Flash workers ✅ after the name-reservation fix · R4 kill -9 mid-turn → partial recovery (headless exec has no mid-turn checkpoint; #5715) · R5 #5712 launch compatibility: paths/labels OK, credentials fail as shipped (onlyCODEWHALE_API_KEYis injected and nothing reads it as a provider credential — high, not fixed here) · R6 Computer uptime 873 s; parent usage 159,542 input tokens; child usage not propagated.Open defects logged for follow-up: #5712 credential plane;
collect_patchbase branch resolves toHEADon Daytona clones (origin/HEAD is a plain ref);.codewhale/statewritten inside the clone unignored; wrapper lacks--max-subagents; non-enum providers rejected bymodels; child usage/cost unknown for Model Studio routes.Evidence
Fixes verified by
cargo check --tests+ 5 focused unit tests on the host and by the R3b/resume re-runs inside the Computer; dogfood re-run reviewer approved; secrets reviewer's major (README credential path) addressed in the last commit. No key material in any artifact; keys were read only from the local Codewhale store into process env.No-Issue: PRD §4.5 flagship proof (agents inside Daytona Computers); refs #5715, #5712
🤖 Generated with Claude Code
https://claude.ai/code/session_014aDEyM2a4pPZ9qqMDrP5YX