Skip to content

feat: raise ControlPlaneError on API error responses instead of returning Error#139

Open
devin-ai-integration[bot] wants to merge 3 commits into
mainfrom
devin/1777574886-controlplane-error-exceptions
Open

feat: raise ControlPlaneError on API error responses instead of returning Error#139
devin-ai-integration[bot] wants to merge 3 commits into
mainfrom
devin/1777574886-controlplane-error-exceptions

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot commented Apr 30, 2026

Summary

Control-plane API functions now raise ControlPlaneError on 4xx/5xx responses instead of returning Union[Model, Error]. This aligns the generated client with the Go SDK and the existing Python domain wrappers (SandboxAPIError, DriveAPIError, VolumeAPIError).

Changes

New unified error hierarchy:

BlaxelAPIError                  # base class (blaxel.core.errors)
├── ControlPlaneError           # control-plane 4xx/5xx (blaxel.core.client.errors)
├── SandboxAPIError             # sandbox domain errors
├── DriveAPIError               # drive domain errors
└── VolumeAPIError              # volume domain errors
  1. src/blaxel/core/errors.py — New BlaxelAPIError base exception with message, status_code, error_code, and response attributes.
  2. src/blaxel/core/client/errors.py — New ControlPlaneError(BlaxelAPIError) that wraps the Error data model and preserves it as error_model.
  3. src/blaxel/core/client/client.py — Added raise_on_error=True flag (opt-out with False for backward compat).
  4. templates/endpoint_module.py.jinja — Updated template to raise ControlPlaneError when client.raise_on_error is True and response type is Error.
  5. 35 generated API files — All _parse_response functions now conditionally raise ControlPlaneError instead of returning Error models.
  6. Domain error classesSandboxAPIError, DriveAPIError, VolumeAPIError now extend BlaxelAPIError.
  7. Wrapper callersdrive.py, volume.py, sandbox/default/sandbox.py, sandbox/sync/sandbox.py updated to catch ControlPlaneError and re-raise as domain-specific errors, with isinstance(response, Error) fallback for raise_on_error=False.
  8. ExportsBlaxelAPIError and ControlPlaneError exported from blaxel.core.
  9. MIGRATION.md — Migration guide with before/after code examples and opt-out instructions.

Review & Testing Checklist for Human

  • Verify error propagation: Call a control-plane API function with invalid input and confirm ControlPlaneError is raised with correct status_code, error_code, and error_model
  • Verify backward compat: Create a client with raise_on_error=False and confirm the old Union[Model, Error] return pattern still works
  • Verify domain wrappers: Confirm DriveInstance.create(), VolumeInstance.create(), SandboxInstance.get() still raise their domain-specific errors (which now extend BlaxelAPIError)
  • Verify BlaxelAPIError catch-all: Confirm except BlaxelAPIError catches ControlPlaneError, SandboxAPIError, DriveAPIError, and VolumeAPIError
  • Regenerate SDK (make sdk-controlplane) and verify the template produces correct output with raise_on_error gating

Notes

  • CI: 1 failing integration test is a pre-existing test bug, not caused by this PR. test_update_lifecycle_with_different_policy_types_preserves_files sends duplicate TTL_IDLE expiration policies (the same policy appears twice in the list), which the API correctly rejects with a 409. Previously this error was silently swallowed because Error was returned as a data model and the test accidentally continued without checking it. Now the error is properly surfaced as SandboxAPIError. The test needs to be fixed separately by removing the duplicate policy entry.
  • The 35 generated API files were batch-transformed to match what the updated Jinja template would produce. After the next make sdk-controlplane regeneration, these files will be overwritten with template-generated versions.
  • The isinstance(response, Error) checks in wrapper code are kept as fallback for raise_on_error=False users — they are unreachable in the default raise_on_error=True path.

Link to Devin session: https://app.devin.ai/sessions/cbe4c13c6e344b8e90d869bc8af2b56b
Requested by: @Joffref


Note

This PR introduces a unified exception hierarchy for Blaxel API errors. Control-plane API functions now raise ControlPlaneError (extending BlaxelAPIError) on 4xx/5xx responses instead of returning Union[Model, Error]. A raise_on_error=True flag on Client allows opt-out. Domain wrappers (drive, volume, sandbox) are updated to catch ControlPlaneError and re-raise as their domain-specific errors, which now also extend BlaxelAPIError. All 35 generated API files and the Jinja template are updated consistently.

Written by Mendral for commit 7c3b72e.

…ning Error

BREAKING CHANGE: Control-plane API functions now raise ControlPlaneError
on 4xx/5xx responses instead of returning Union[Model, Error].

- Add BlaxelAPIError base class in src/blaxel/core/errors.py
- Add ControlPlaneError(BlaxelAPIError) in client/errors.py
- Add raise_on_error=True flag to Client (opt-out with False)
- Update Jinja template to raise on Error responses
- Transform all 35 generated API files to raise ControlPlaneError
- Make SandboxAPIError, DriveAPIError, VolumeAPIError extend BlaxelAPIError
- Wrap callers in try/except ControlPlaneError (drive, volume, sandbox, tools)
- Export BlaxelAPIError and ControlPlaneError from blaxel.core
- Add MIGRATION.md with upgrade instructions

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

mendral-app[bot]

This comment was marked as outdated.

Ensures update_metadata, update_ttl, update_lifecycle, create, and
delete all translate ControlPlaneError into SandboxAPIError, consistent
with the get() method.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@mendral-app mendral-app Bot left a comment

Choose a reason for hiding this comment

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

Needs attention

The follow-up commit correctly wraps the remaining sandbox API calls. One inconsistency: src/blaxel/core/sandbox/sync/sandbox.py:458 — the sync _delete_sandbox_by_name is missing the if response is None: raise ValueError(...) guard that exists in the async version at sandbox/default/sandbox.py:535. With raise_on_error=False, the sync version silently returns None instead of raising, causing a type mismatch for callers. Add the same None check before return response.

Tag @mendral-app with feedback or questions. View session

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

@mendral-app Good catch — added the if response is None: raise ValueError(...) guard to the sync _delete_sandbox_by_name in 7ca3099, matching the async version.

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