Skip to content

Deleting from the inspect drawer shows "Inspect failed: No such container" after the delete succeeds #174

Description

@rodriguecyber

Difficulty: Beginner–Intermediate · You'll need: React, TanStack Query · Size: ~20 lines

What's going on

Deleting a container from the inspect drawer works — and then immediately reports a failure.

Steps to reproduce

  1. Open a container's inspect drawer
  2. Click Delete, confirm
  3. A success toast appears: "Container deleted"
  4. The drawer stays open and replaces its contents with:
Inspect failed

docker inspect 924f6917da91: Error response from daemon:
No such container: 924f6917da91607ff636bc1de91a0306596bf71825c4705f2f3f2ff07d53bad7

The deletion succeeded. The error is the dashboard inspecting a container it just removed.

Why it matters

The operator sees a success toast and a red failure banner for the same action, seconds apart. The natural reading is that something went half-wrong — so they retry, or go and check by hand. For the one irreversible operation in the product, that is the worst possible moment to be ambiguous about what happened.

It also sends two doomed requests to the agent (retry: 1 is set in apps/web/src/app/providers.tsx), each one a full container.inspect round trip over the WebSocket to run docker inspect on a container that no longer exists.

Root cause

Two defects, both real, either of which alone produces the symptom.

1. The drawer is never closed after a successful delete

apps/web/src/components/ContainerInspectDrawer.tsx:145-156 fires the action and nothing else:

onClick={() => onAction?.(container, 'remove')}

onAction is useContainerCommands.run. The drawer's open/closed state lives in the page:

// apps/web/src/features/containers/ContainersPage.tsx
const [inspecting, setInspecting] = useState<ContainerRow | null>(null)

and setInspecting(null) is only reachable from the drawer's onClose. So after a successful delete the drawer stays mounted with inspecting still pointing at the dead container.

2. The inspect query is never evicted from the cache

apps/web/src/hooks/useContainerAction.ts invalidates only the list:

onSuccess: async (_result, variables) => {
  await queryClient.invalidateQueries({
    queryKey: containersQueryKey(variables.hostId),
  })
}

containerInspectQueryKey(hostId, containerId) is untouched. With the drawer still mounted, useContainerInspect stays enabled, its staleTime: 5_000 lapses, and it refetches — hitting a container that is gone.

The stale entry also outlives the drawer: it sits in the cache keyed by container id until garbage-collected.

The fix

Defect 2 is unambiguous. In useContainerAction, when the action was remove, also drop the inspect entry:

if (variables.action === 'remove') {
  queryClient.removeQueries({
    queryKey: containerInspectQueryKey(variables.hostId, variables.containerId),
  })
}

removeQueries, not invalidateQueries — invalidating would refetch the deleted container, which is the bug.

Defect 1 needs a decision. The drawer must close, but only on a successful remove:

  • not on cancel — the user dismissed the confirmation and expects the drawer as they left it
  • not on failure — that is exactly when they need the drawer's error visible
  • not on start/stop/restart — the point of those is watching the state change in place

onAction currently returns void, so the drawer cannot tell success from cancel from failure. Options:

  • A. Have useContainerCommands.run return the outcome (a boolean, or the ContainerActionResult), widen the onAction prop to Promise<...> | void, and let the drawer await it and close on success. Keeps the decision next to the button.
  • B. Give the drawer an onRemoved callback that the page wires to setInspecting(null). More explicit, but each page must remember to pass it — three pages today.

A is probably right, for the same reason the delete confirmation lives in useContainerCommands rather than in each component: one shared path, no per-surface bookkeeping. But argue for whichever you pick.

Watch out for

  • The row menu path. Delete is also in ContainerTable's dropdown, where no drawer is open. Whatever you change must not break that.
  • ContainerLogsDrawer. Deleting a container while its logs are streaming has the same shape of problem — worth checking, and a fine follow-up issue if it is broken too.
  • Not optimistic. The row must still disappear only after the server confirms. Do not "fix" this by hiding things before the result arrives.

How to verify

npm run docker:infra && npm run dev:server && npm run dev:web
docker run -d --name test-delete nginx

Then check all four paths:

  1. Open the drawer, delete, confirm → drawer closes, success toast, row gone, no error banner
  2. Open the drawer, delete, cancel → drawer stays open, unchanged
  3. Delete from the row menu with no drawer open → still works
  4. Force a failure (delete a running container without ticking force) → drawer stays open and shows Docker's message

Watch the network tab in case 1: there must be no GET /containers/:id/inspect after the delete returns.

Done when

  • A successful delete from the drawer closes it and shows only the success toast
  • No inspect request is sent for a deleted container
  • The inspect cache entry is removed, not invalidated
  • Cancel and failure both leave the drawer open
  • Deleting from the row menu is unaffected

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:webThe React dashboard (apps/web)bugSomething isn't workinggood first issueGood for newcomers

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions