Skip to content

fix(api): raise domain exceptions where the caller needs the message - #50

Open
ExtraToast wants to merge 2 commits into
mainfrom
fix/domain-exceptions-for-conflicts
Open

fix(api): raise domain exceptions where the caller needs the message#50
ExtraToast wants to merge 2 commits into
mainfrom
fix/domain-exceptions-for-conflicts

Conversation

@ExtraToast

Copy link
Copy Markdown
Contributor

What

Follow-up to JorisJonkers-dev/kotlin-spring-commons#34, which stops the shared advice returning IllegalStateException's message. error(…) throws exactly that, so every error(…) in this service now produces a generic 409.

For most of them that is correct — "No handler registered for X", "not visible immediately after create", "empty response from gateway", "workspace missing for session " are internal invariants and should stay withheld. Eight are not, and move onto the exception types this repo already uses elsewhere.

Conflicts the caller caused and needs to readDomainException with a code, so the message survives as a 400:

handler code
CreateProjectCommandHandler SLUG_ALREADY_IN_USE
CreateRepositoryCommandHandler REPOSITORY_ALREADY_REGISTERED

Lookups of an entity the caller namedNotFoundException, which is both the right status and a detail worth reading. These were 409s:

  • LinkRepositoryToProjectCommandHandler — project, repository
  • AddGithubLinkCommandHandler — project
  • OpenPullRequestCommandHandler — workspace
  • AgentSessionController — session (×2), workspace

Deliberately unchanged

SendUserInputCommandHandler's generation conflict and AgentSessionController.restartConflict. agents-ui reads a 409 from the restart path to drive its reattach flow (workspaces.ts, err.status === 409). That contract is status-only — it never reads the detail text — so the generic detail does not affect it, and neither status changes here.

Status changes

Six paths move 409 → 404. Nothing in api/src/test, api/src/integrationTest or the UIs asserts a 409 on any of them; the only 409 assertions are on the restart-conflict path above, which is an explicit ResponseEntity, not an exception.

Tests

Updated the five handler tests that asserted IllegalStateException to assert the new type and that the message still carries the slug / URL / id — the point of the change is that these messages survive, so the tests check the message, not just the type. Added an AgentSessionController test asserting the unknown-session path is a 404 whose detail still names what was missing.

Verification

Not built locally./gradlew here resolves dev.jorisjonkers:kotlin-commons-*:0.7.1 from GitHub Packages and this workstation shell has no gpr.user / gpr.token, so compilation fails at dependency resolution before reaching any source. Relying on CI for compile, test, ktlint and detekt; happy to re-run locally if someone points me at credentials.

`error(…)` throws IllegalStateException, whose message the shared advice
now withholds (JorisJonkers-dev/kotlin-spring-commons#34). Most uses here
are internal invariants and should stay withheld — "No handler registered
for X", "not visible immediately after create", "empty response from
gateway". These are not.

Two conflicts the caller caused and needs to read become DomainExceptions
with codes, so the message survives as a 400:

- CreateProjectCommandHandler       SLUG_ALREADY_IN_USE
- CreateRepositoryCommandHandler    REPOSITORY_ALREADY_REGISTERED

Six lookups of an entity the caller named become NotFoundException, which
is both the right status and a detail worth reading — they were 409s:

- LinkRepositoryToProjectCommandHandler  project, repository
- AddGithubLinkCommandHandler            project
- OpenPullRequestCommandHandler          workspace
- AgentSessionController                 session (x2), workspace

Deliberately unchanged: SendUserInputCommandHandler's generation conflict
and AgentSessionController.restartConflict, which the UI reads as a 409 to
drive its reattach flow. That contract is status-only, so the generic
detail does not affect it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ExtraToast ExtraToast added type: bug Something is broken or behaving incorrectly. area: agents Agent runtime, agent APIs, tools, prompts, or UI. component: api HTTP API, OpenAPI contract, or API client concern. priority: P2 Medium; normal planned work. labels Aug 27, 2026
The added NotFoundException took stageInput to three throws, one over
detekt's ThrowsCount limit. The session lookup and ownership check it
inlined were already requireSessionInWorkspace verbatim, so calling that
removes the duplication and the violation together.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: agents Agent runtime, agent APIs, tools, prompts, or UI. component: api HTTP API, OpenAPI contract, or API client concern. priority: P2 Medium; normal planned work. type: bug Something is broken or behaving incorrectly.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant