Summary
The UI issues /container_state and /container_log requests for nodes that legitimately have no
container yet, which the backend answers as "not ready." Two problems: (1) the UI makes requests it
should be able to skip, and (2) it renders the not-ready response as a user-facing error.
Status update: the backend side (TangleML/tangle#297) now returns 409 container_not_ready
(no Bugsnag) instead of a 500, so the Bugsnag storm is resolved. What remains is UI-side:
unnecessary requests, a retry storm, and an error box for an expected state.
Corrected root cause
The original framing ("PENDING is missing from CONTAINER_STATUSES_PRE_LAUNCH, so PENDING fetches
race the container row") was investigated at the backend code level and is not accurate:
- PENDING is a red herring. The orchestrator writes
container_execution_status = PENDING and
the container link in the same transaction (atomic), so a PENDING node effectively always has a
container row. Fetching during PENDING is correct and desirable (early logs). Do not add PENDING
to the skip set — it would suppress logs for containers that are about to run.
The requests that actually hit the not-ready path are for nodes where container_execution IS NULL:
- Graph / sub-pipeline nodes — never launch a container; their status is permanently
None.
This is the dominant, guaranteed source.
- Pre-launch container nodes —
QUEUED / WAITING_FOR_UPSTREAM before the orchestrator launches.
The two guards in the codebase are inconsistent, which is why these slip through:
useFetchContainerExecutionState (src/services/executionService.ts ~L108):
!!executionId && (!status || !CONTAINER_STATUSES_PRE_LAUNCH.has(status)) — the !status ||
clause fetches when status is unknown/None, i.e. for graph nodes.
Logs/shouldStatusHaveLogs (src/.../TaskOverview/logs.tsx ~L75): if (!status) return false
— correctly skips when status is unknown. (Different behavior from the state hook.)
Where the unnecessary requests come from
- LogsEventsOverlaySection.tsx (~L33) — biggest offender: calls
useFetchContainerExecutionState(executionId, backendUrl) with no status argument and no
subgraph guard. So status is undefined → the !status clause fires → it fetches for every
node, including graph nodes and pre-launch container nodes. The component even receives status
as a prop (~L25) but doesn't pass it through.
- ExecutionDetails.tsx (~L40) — already correct: guards subgraph nodes
(isSubgraph ? undefined : executionId) and passes status. Keep as the reference pattern. Its
only remaining issue is the error box below.
- AiChat bridges — runBridge.ts (~L183) and debugBridge.ts (~L46) call
fetchContainerExecutionState directly with no status guard (try/catch swallows the result, but
the request still hits the backend).
Other gaps
- Retry storm —
retryUnlessAuth (src/services/executionService.ts ~L30) retries up to 3× on
any non-auth error → one not-ready response = 4 requests. It should not retry on 409/404.
- ExecutionDetails.tsx (~L140) renders
containerStateError as a red "Failed to load container
state" InfoBox → an expected not-ready state shows as a user-facing error.
- fetchContainerLog (src/services/executionService.ts ~L93) does a raw
fetch(...).json() with
no status/error handling, so a 409/500 body is parsed as if it were a log response.
Extra findings
https://docs.google.com/document/d/1HlZRkwKZ5KFHE_HvMPOQA7Atkj3fYhqfjQ4DkHT_D4E/edit?usp=sharing
Suggested solution (in priority order)
- Stop fetching for nodes that can't have a container. Make
LogsEventsOverlaySection pass
status and skip subgraph nodes, matching ExecutionDetails. Unify the two guards so
container-state and container-log fetches share one rule that skips when status is unknown/None
or in CONTAINER_STATUSES_PRE_LAUNCH. (Do not add PENDING to the skip set.)
- Treat
409 container_not_ready as a non-error. Don't set error state; keep polling and show
a "waiting for container" state instead of the red InfoBox in ExecutionDetails.tsx.
- Don't retry on not-ready. Update
retryUnlessAuth to skip 409/404; only retry
network/5xx.
- Gate the AiChat bridges on status the same way (skip subgraph / unknown / pre-launch).
- Harden
fetchContainerLog to use fetchWithErrorHandling and handle the not-ready response
explicitly.
Acceptance
- Selecting/polling a graph node or a pre-launch node makes no
container_state/container_log
request (or, if one slips through, the 409 is handled silently).
- "Not ready" never surfaces as an error box; it shows a pending/waiting state.
- No retry storm on not-ready responses.
- AiChat bridges gate their fetches on status/node type.
Backend context
Full analysis of why the columns can legitimately be out of sync (graph nodes, pre-launch nodes,
admin override) and why the orchestrator never produces a PENDING-without-container window is in
the investigation doc backing TangleML/tangle#297. The backend fix keys off the container link
(not the status), so it already returns 409 for every container-less case regardless of status.
Summary
The UI issues
/container_stateand/container_logrequests for nodes that legitimately have nocontainer yet, which the backend answers as "not ready." Two problems: (1) the UI makes requests it
should be able to skip, and (2) it renders the not-ready response as a user-facing error.
Status update: the backend side (TangleML/tangle#297) now returns 409
container_not_ready(no Bugsnag) instead of a 500, so the Bugsnag storm is resolved. What remains is UI-side:
unnecessary requests, a retry storm, and an error box for an expected state.
Corrected root cause
The original framing ("PENDING is missing from
CONTAINER_STATUSES_PRE_LAUNCH, so PENDING fetchesrace the container row") was investigated at the backend code level and is not accurate:
container_execution_status = PENDINGandthe container link in the same transaction (atomic), so a PENDING node effectively always has a
container row. Fetching during PENDING is correct and desirable (early logs). Do not add PENDING
to the skip set — it would suppress logs for containers that are about to run.
The requests that actually hit the not-ready path are for nodes where
container_execution IS NULL:None.This is the dominant, guaranteed source.
QUEUED/WAITING_FOR_UPSTREAMbefore the orchestrator launches.The two guards in the codebase are inconsistent, which is why these slip through:
useFetchContainerExecutionState(src/services/executionService.ts ~L108):!!executionId && (!status || !CONTAINER_STATUSES_PRE_LAUNCH.has(status))— the!status ||clause fetches when status is unknown/None, i.e. for graph nodes.
Logs/shouldStatusHaveLogs(src/.../TaskOverview/logs.tsx ~L75):if (!status) return false— correctly skips when status is unknown. (Different behavior from the state hook.)
Where the unnecessary requests come from
useFetchContainerExecutionState(executionId, backendUrl)with nostatusargument and nosubgraph guard. So
statusisundefined→ the!statusclause fires → it fetches for everynode, including graph nodes and pre-launch container nodes. The component even receives
statusas a prop (~L25) but doesn't pass it through.
(
isSubgraph ? undefined : executionId) and passesstatus. Keep as the reference pattern. Itsonly remaining issue is the error box below.
fetchContainerExecutionStatedirectly with no status guard (try/catch swallows the result, butthe request still hits the backend).
Other gaps
retryUnlessAuth(src/services/executionService.ts ~L30) retries up to 3× onany non-auth error → one not-ready response = 4 requests. It should not retry on
409/404.containerStateErroras a red "Failed to load containerstate" InfoBox → an expected not-ready state shows as a user-facing error.
fetch(...).json()withno status/error handling, so a
409/500body is parsed as if it were a log response.Extra findings
https://docs.google.com/document/d/1HlZRkwKZ5KFHE_HvMPOQA7Atkj3fYhqfjQ4DkHT_D4E/edit?usp=sharing
Suggested solution (in priority order)
LogsEventsOverlaySectionpassstatusand skip subgraph nodes, matchingExecutionDetails. Unify the two guards socontainer-state and container-log fetches share one rule that skips when status is unknown/None
or in
CONTAINER_STATUSES_PRE_LAUNCH. (Do not add PENDING to the skip set.)409 container_not_readyas a non-error. Don't set error state; keep polling and showa "waiting for container" state instead of the red InfoBox in
ExecutionDetails.tsx.retryUnlessAuthto skip409/404; only retrynetwork/5xx.
fetchContainerLogto usefetchWithErrorHandlingand handle the not-ready responseexplicitly.
Acceptance
container_state/container_logrequest (or, if one slips through, the
409is handled silently).Backend context
Full analysis of why the columns can legitimately be out of sync (graph nodes, pre-launch nodes,
admin override) and why the orchestrator never produces a
PENDING-without-container window is inthe investigation doc backing TangleML/tangle#297. The backend fix keys off the container link
(not the status), so it already returns
409for every container-less case regardless of status.