Skip to content

chore: API Server - Return 409 instead of 500 when container execution not yet available#297

Merged
morgan-wowk merged 1 commit into
masterfrom
07-16-fix_return_409_not_500_when_container_execution_not_yet_available
Jul 17, 2026
Merged

chore: API Server - Return 409 instead of 500 when container execution not yet available#297
morgan-wowk merged 1 commit into
masterfrom
07-16-fix_return_409_not_500_when_container_execution_not_yet_available

Conversation

@morgan-wowk

@morgan-wowk morgan-wowk commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Closes #296

Problem

Three read endpoints return 500 + a Bugsnag notification when an ExecutionNode exists but has no linked ContainerExecution row:

  • GET /api/executions/{id}/container_state
  • GET /api/executions/{id}/container_log
  • GET /api/executions/{id}/stream_container_log

They gate on the relationship (if not execution.container_execution) and raised RuntimeError/ApiServiceError, which had no handler in api_router.py, so they fell through to the catch-all Exception handler → 500 + Bugsnag.

A node can legitimately have no container row in several committed states — container_execution_status and container_execution are independent columns with no invariant coupling them:

  • Graph / sub-pipeline nodes — never launch a container; their status stays None permanently. The frontend polls these (its fetch fires on !status), so this is the dominant, guaranteed source of the noise.
  • Pre-launch container nodesQUEUED / WAITING_FOR_UPSTREAM before the orchestrator launches; hit by direct API callers (tangle-deploy, operator scripts).
  • Admin status override (PUT /api/admin/execution_node/{id}/status) — can set any status, including PENDING, without a container link.

In all of these the condition is expected, not a server fault. The recurring 500s also buried genuine execution does not exist 404s under expected-state noise. (Note: the orchestrator's normal launch/reuse paths do not produce this — they write status and link atomically in one transaction; full analysis in the investigation doc.)

Changes

  • errors.py — new ContainerExecutionNotReadyError carrying execution_status, distinct from ItemNotFoundError (which means the execution itself is absent).
  • api_server_sql.py — raise it at all three sites when the node exists but container_execution is None, unifying the previous RuntimeError + ApiServiceError. The existing graceful SYSTEM_ERROR-with-no-row path is unchanged.
  • api_router.py — handler mapping it to 409 Conflict with a machine-readable body: {"reason": "container_not_ready", "execution_status": "<status-or-null>", "message": "..."}.

The fix keys off the container link (not the status), so it covers every case above regardless of the node's status.

System impact

The dedicated ContainerExecutionNotReadyError handler is more specific than the catch-all Exception handler in api_server_main.py, so it takes precedence and Bugsnag is not notified on this path. Genuine execution does not exist still returns 404.

Before After
Node exists, no container row (graph node, pre-launch, etc.) 500 + Bugsnag event 409, no Bugsnag
Execution absent 404 404 (unchanged)

Tests

Two focused service-level tests: a node with no container row raises ContainerExecutionNotReadyError (with execution_status populated), and a genuinely absent execution still raises ItemNotFoundError.

Copy link
Copy Markdown
Collaborator Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@morgan-wowk
morgan-wowk force-pushed the 07-16-fix_return_409_not_500_when_container_execution_not_yet_available branch 2 times, most recently from 01a0cb1 to 81eeead Compare July 16, 2026 18:18
@morgan-wowk
morgan-wowk marked this pull request as ready for review July 16, 2026 20:05
@morgan-wowk
morgan-wowk requested a review from Ark-kun as a code owner July 16, 2026 20:05
@morgan-wowk
morgan-wowk force-pushed the 07-16-fix_return_409_not_500_when_container_execution_not_yet_available branch 2 times, most recently from e123ec2 to 176effc Compare July 16, 2026 20:31
Comment thread cloud_pipelines_backend/api_router.py Outdated
):
with contextual_logging.logging_context(execution_id=exc.execution_node_id):
_logger.info(
"Container execution not ready (status=%s)", exc.execution_status

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

nit: We can bake execution status into logging_context above

@Ark-kun

Ark-kun commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

During that window the node's container_execution_status can already be PENDING while execution.container_execution is still None.

Are you sure this can happen? At first glance this looks like it would be a bug.

@Ark-kun

Ark-kun commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

handler mapping it to 409 Conflict

Would 404 be more appropriate? The container sub-resource does not exist (yet).

Comment thread cloud_pipelines_backend/api_router.py Outdated
request: fastapi.Request, exc: errors.ContainerExecutionNotReadyError
):
with contextual_logging.logging_context(execution_id=exc.execution_node_id):
_logger.info(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not sure we need to log this. This is a common occurrence.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thank you for letting me know. I was on the fence about the log.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good call — removed the log line (and the now-unused _logger/logging import). It was a common, expected occurrence and the 409 response is self-describing.

@Ark-kun Ark-kun changed the title fix: return 409 instead of 500 when container execution not yet available chore: API Server - Return 409 instead of 500 when container execution not yet available Jul 16, 2026

Copy link
Copy Markdown
Collaborator Author

handler mapping it to 409 Conflict

Would 404 be more appropriate? The container sub-resource does not exist (yet).

​I was thinking 404 but it can be confused with the execution entity / DB record not being found. 409 is an alternative that could be used to say the route was valid, and found a record, but could not produce the intended result

Copy link
Copy Markdown
Collaborator Author

During that window the node's container_execution_status can already be PENDING while execution.container_execution is still None.

Are you sure this can happen? At first glance this looks like it would be a bug.

​I shall verify

@Ark-kun Ark-kun left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved with comments.

Copy link
Copy Markdown
Collaborator Author

During that window the node's container_execution_status can already be PENDING while execution.container_execution is still None.

Are you sure this can happen? At first glance this looks like it would be a bug.

​This has been disproven and the statement will be removed from this PR description. Tangle UI actively polls for container execution information for subgraph nodes when said data will never exist. An issue has been created in Tangle UI: TangleML/tangle-ui#2539

…able

An ExecutionNode exists before its ContainerExecution row is created and
linked by the orchestrator. During that window (status PENDING, no pod yet),
the container_state / container_log / stream_container_log endpoints raised
RuntimeError / ApiServiceError, surfacing as uncaught 500s and Bugsnag noise.

Add ContainerExecutionNotReadyError (distinct from ItemNotFoundError, which
means the execution itself is absent), raise it at all three sites, and map
it to a 409 Conflict with a machine-readable body. The dedicated handler
takes precedence over the catch-all Exception handler, so Bugsnag is not
notified. Genuine 'execution does not exist' still returns 404.
@morgan-wowk
morgan-wowk force-pushed the 07-16-fix_return_409_not_500_when_container_execution_not_yet_available branch from 176effc to 45b7a02 Compare July 16, 2026 22:39

Copy link
Copy Markdown
Collaborator Author
  • Smoke tested locally with OSS environment
  • Smoke tested with known upstream consumers

@morgan-wowk
morgan-wowk merged commit e7fb65e into master Jul 17, 2026
5 of 9 checks passed
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.

container_state/container_log/stream endpoints return 500 (Bugsnag noise) when container execution not yet available

2 participants