605a7421e1
* docs(sandbox): document derived pool-name namespace collisions Pool.apply() without name= (including via Sandbox.create(image, ...)) derives a deterministic cua-<hash> pool name from the image and configuration, and uses it as the namespace. Identical configurations hash to the same namespace for every account, so a common configuration can collide with a namespace another credential owns and fail with 403 RBAC denials that fresh claim names cannot work around. Add a section to the create-pool guide explaining the collision and the fix: pass a unique explicit name= to Pool.apply() and claim from that pool, or pass the named pool to Sandbox.create(pool=...). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012kbiBCs8Wo3LtXCTEvV7dF * feat(cua-sandbox)!: require explicit globally-unique pool names Pool names double as Fleet namespaces and are globally unique across accounts, so the SDK must never invent them. Previously Pool.apply() without name= derived a deterministic cua-<sha256[:12]> name from the image and configuration, so common configurations collided with namespaces owned by other accounts and failed with opaque 403s that fresh claim names could not work around. - Pool.apply() (async and sync) now requires a non-empty name=. - Sandbox.create(image) with Fleet credentials raises a ValueError directing callers to Pool.apply(image, name=...) followed by Sandbox.create(pool=..., name=...), instead of silently applying a derived pool. - Sandbox.ephemeral(image) provisions its disposable pool under a random cua-eph-<hex> name; keep_pool=True now requires name= so later runs can find the kept pool. Claim names are unchanged: they are namespace-scoped, so the Fleet SDK's random claim-<petname> generation remains safe. - 403s from pool/template reconciliation are mapped to a new PoolAccessDeniedError (exported from cua_sandbox and cua) that explains the global-uniqueness rule and likely causes; the rollback after a failed template reconcile is best-effort so it no longer masks the original error. - README, the create-pool and Minecraft guides, and the Android RPS benchmark are updated to the explicit-pool pattern. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012kbiBCs8Wo3LtXCTEvV7dF * fix(cua-sandbox): point pool-denied error at a new name and Discord support Drop the speculative sizing/template-restriction clause from PoolAccessDeniedError. The actionable advice is: try a new pool name, and contact support on Discord (https://discord.gg/mVnXXpdE85) if that does not work. Update the quoted message in the create-pool guide. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012kbiBCs8Wo3LtXCTEvV7dF * feat(cua-sandbox): adopt upstream Fleet SDK pool-access-denied error when available Newer Fleet SDKs raise SdkError.PoolAccessDenied from the Rust core for 403s on pool-namespace writes, so every language binding shares one message. Alias PoolAccessDeniedError to that type and construct it directly when the installed cua-fleet provides it, keeping the local PermissionError subclass and Status-403 mapping as the fallback for older SDKs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012kbiBCs8Wo3LtXCTEvV7dF * fix(cua-sandbox): render pool-denial fallback message identically to the Rust SDK The local PoolAccessDeniedError fallback repr-quoted the operation ('create pool') while cyclops-sdk's SdkError::PoolAccessDenied renders it unquoted. Match the upstream rendering exactly so the message is byte-identical whether it originates locally or from cua-fleet, and update the docs sample output to the shared rendering. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>