Skip to content

🤖 fix: return an empty namespaced LIST where no Coder backend serves the namespace - #214

Merged
ThomasK33 merged 3 commits into
mainfrom
fix/209-empty-namespace-list
Oct 3, 2026
Merged

ThomasK33 merged 3 commits into
mainfrom
fix/209-empty-namespace-list

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

Summary

Fixes #209. A namespaced LIST of coderworkspaces, codertemplates, or codertemplateversions in a namespace that no Coder backend serves now returns 200 with an empty list instead of an error. With this change, a namespace without a CoderControlPlane finishes deleting. GET, CREATE, UPDATE, DELETE and the subresources keep their current errors.

Background

The namespace controller lists every deletable resource type in a namespace before it removes the namespace. The aggregated API answered that LIST with 503 ServiceUnavailable ("no eligible CoderControlPlane instances found in namespace ...") since #53. The namespace controller treats the error as remaining content, so every namespace without an eligible control plane stayed Terminating. That included an empty namespace, and the coder namespace after its control plane was gone.

Implementation

  • internal/aggregated/coder: ClientForNamespace marks the errors that mean "no backend serves this namespace" with an unexported wrapper type. coder.IsNamespaceNotServed(err) detects the mark. The wrapper unwraps to the original StatusError, so every other verb returns the same status and message as before. Two errors get the mark:
    • all mode: the named namespace contains no CoderControlPlane at all (the 503). A namespace whose control plane exists but is not eligible keeps the unmarked 503.
    • Standalone mode (--app=aggregated-apiserver): a namespace other than --coder-namespace (the 400). This mode had the same hang for every other namespace.
  • internal/aggregated/storage: listsUnservedNamespace(ctx, err) is true only when the request names a namespace and the error carries the mark. The three LIST paths then return a typed list with items: [], without a Coder request.
  • Not marked, so unchanged: an all-namespaces request, a control plane that exists but is not eligible (for example operatorAccessReady=false), two eligible control planes in one namespace (400), token Secret or URL problems of an eligible control plane, and an unpinned standalone provider.

Decisions on cluster-wide LIST and WATCH

  • All-namespaces LIST stays unchanged. With no eligible control plane in any namespace, it still returns 503. The namespace controller never sends a cluster-wide LIST, so 🤖 Namespace deletion hangs: the aggregated API answers LIST with 503 when the namespace has no control plane #209 does not need it. Keeping the 503 keeps the clear "no eligible CoderControlPlane" message for kubectl get -A on a new install. The garbage collector and quota controllers only log the failing informer.
  • WATCH stays unchanged. It never calls the provider (it serves local broadcaster events), so it already succeeds in a namespace without a control plane and across all namespaces. A new test pins that.
  • "No control plane at all", not "no eligible control plane". The operator sets operatorAccessReady=false on transient Postgres or bootstrap errors (reconcileOperatorAccess). If such a short outage emptied the LIST, watch clients would see every object as deleted. So a namespace that contains any CoderControlPlane keeps the 503. Namespace deletion still finishes: the namespace controller continues through all resource types after a LIST error (deleteAllContent in kube-controller-manager), so it deletes the control plane in the same pass. The next pass gets empty lists. The e2e-kind job checks this order: it deletes the coder namespace while its control plane still exists.

E2E driver

The namespace-deletion phase of hack/e2e-workspace-lifecycle.sh (added by #210) now waits until the namespace is gone (NotFound). It no longer accepts NamespaceContentRemaining=False and NamespaceFinalizersRemaining=False as success. From the first poll after the delete, while the namespace exists, the driver logs its phase and True conditions each time they change. A run thus shows NamespaceDeletionContentFailure (the aggregated LIST 503 while the control plane still exists) and the diagnostics for a failure. The offline tests add an ns-stays-terminating scenario: a namespace that stays Terminating (the #209 symptom) now fails the phase with a timeout.

Validation

  • Tests first: TestNamespacedListInUnservedNamespaceReturnsEmptyList failed on main with the 🤖 Namespace deletion hangs: the aggregated API answers LIST with 503 when the namespace has no control plane #209 error, then passed with the fix.
  • Fix round 1, tests first: a namespaced LIST with a control plane that has operatorAccessReady=false must return 503, and the provider must not mark that error. Both tests failed before the change.
  • New tests also cover: other verbs keep 503 as a top-level StatusError, all-namespaces LIST keeps 503, two eligible control planes keep 400, an unreadable token Secret and an unpinned standalone provider stay errors, and WATCH succeeds.
  • Local gates on the PR head tree: make verify-vendor, make test, make test-integration, make build, make lint, go test -race on internal/aggregated/coder and internal/aggregated/storage, bash hack/e2e-workspace-lifecycle_test.sh (all offline tests pass), make docs-check, shellcheck, markdownlint and cspell on the changed docs.
  • Kind (kindest/node v1.32.0, --app=all with the APIService, no CoderControlPlane). With an image built from main (dabb113), an empty namespace stayed Terminating with NamespaceDeletionContentFailure: Failed to delete all resource types, 2 remaining: no eligible CoderControlPlane .... After the switch to the image from this branch, the same stuck namespace disappeared within 5 s. A fresh empty namespace was deleted in 5 s. In that namespace, LIST returned items: [], while GET, DELETE and CREATE still returned ServiceUnavailable, and kubectl get coderworkspaces -A still returned 503. The full e2e-kind CI job runs the updated driver phase on this PR.
Kind transcript, before the fix (image from main), condensed
$ k create namespace e2e-probe-empty
namespace/e2e-probe-empty created
$ k get coderworkspaces.aggregation.coder.com,codertemplates.aggregation.coder.com -n e2e-probe-empty
Error from server (ServiceUnavailable): no eligible CoderControlPlane instances found in namespace "e2e-probe-empty"
Error from server (ServiceUnavailable): no eligible CoderControlPlane instances found in namespace "e2e-probe-empty"
$ k delete namespace e2e-probe-empty --wait=false
namespace "e2e-probe-empty" deleted
(60 s later)
$ k get ns e2e-probe-empty   # phase and True conditions
{"phase":"Terminating","conditions":[{"type":"NamespaceDeletionContentFailure","message":"Failed to delete all resource types, 2 remaining: no eligible CoderControlPlane instances found in namespace \"e2e-probe-empty\", no eligible CoderControlPlane instances found in namespace \"e2e-probe-empty\""}]}
Kind transcript, after the fix (image from this branch), condensed
$ kubectl wait --for=delete namespace/e2e-probe-empty --timeout=900s
namespace/e2e-probe-empty condition met
e2e-probe-empty gone after 5 s of waiting
$ k create namespace e2e-probe-fresh
$ k get coderworkspaces,codertemplates,codertemplateversions -n e2e-probe-fresh
No resources found in e2e-probe-fresh namespace.
$ k get --raw /apis/aggregation.coder.com/v1alpha1/namespaces/e2e-probe-fresh/coderworkspaces
{"kind":"CoderWorkspaceList","apiVersion":"aggregation.coder.com/v1alpha1","metadata":{},"items":[]}
$ k get coderworkspaces coder.alice.dev -n e2e-probe-fresh
Error from server (ServiceUnavailable): no eligible CoderControlPlane instances found in namespace "e2e-probe-fresh"
$ k delete codertemplates coder.starter -n e2e-probe-fresh
Error from server (ServiceUnavailable): no eligible CoderControlPlane instances found in namespace "e2e-probe-fresh"
$ k create -f tpl.json
Error from server (ServiceUnavailable): error when creating "tpl.json": no eligible CoderControlPlane instances found in namespace "e2e-probe-fresh"
$ k get coderworkspaces -A
Error from server (ServiceUnavailable): no eligible CoderControlPlane instances found across all namespaces
$ k delete namespace e2e-probe-fresh --wait=false && kubectl wait --for=delete namespace/e2e-probe-fresh --timeout=120s
namespace/e2e-probe-fresh condition met
e2e-probe-fresh gone after 5 s

Risks

Low to medium, limited to aggregated LIST responses. A client that relied on the LIST 503 to detect a namespace without a control plane now gets an empty list. A namespace with a control plane that is not ready keeps the 503. The reference docs describe both. GET and write paths are unchanged.


Generated with mux • Model: anthropic:claude-opus-5-5 • Thinking: high

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-03T16:51:16.289289Z 13405d0 Manual request
🔒 Security Review ✅ Completed 2026-10-03T16:48:38.874449Z 13405d0 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: 6e4d5e98c2

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@ThomasK33

Copy link
Copy Markdown
Member Author

Readiness record for head 6e4d5e98c205b02f07373ac11283030854cc4970:

  • CI: every check passed on this head. "E2E (Kind + CNPG + Templates)" ran the full driver: "namespace coder deleted in 12s", then "PASS: workspace lifecycle".
  • Reviews: 2 (the automatic Codex code review on PR open: completed, no findings; the Codex security review: no security issues). No review threads. No fix rounds were needed.
  • Independent assessment (one clean-context pass): ready with tracked follow-ups.
  • Follow-ups: 🤖 Aggregated API: decide two edge cases of the empty namespaced LIST (#209 follow-up) #215 (empty LIST while a control plane exists but is not eligible; unconfigured standalone server).
  • Not merged: a maintainer merges.

Generated with mux • Model: anthropic:claude-opus-5-5 • Thinking: high

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: cbf77c219a

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: cbf77c219a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…e namespace (#209)

Change-Id: I2df6cd4bc4b1e5c1362a422beba4c663183f189c
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…t eligible

Change-Id: Ia4dc887f37a8b6516dd035c24649d98aae485e37
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: I3515ac9d98be4a5416aff96cf74e7d2dc1193d1b
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33
ThomasK33 force-pushed the fix/209-empty-namespace-list branch from cbf77c2 to 13405d0 Compare October 3, 2026 16:43
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: 13405d04a7

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 13405d04a7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/aggregated/storage/workspace.go
@ThomasK33
ThomasK33 added this pull request to the merge queue Oct 3, 2026
Merged via the queue into main with commit b567e86 Oct 3, 2026
13 checks passed
@ThomasK33
ThomasK33 deleted the fix/209-empty-namespace-list branch October 3, 2026 17:17
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.

🤖 Namespace deletion hangs: the aggregated API answers LIST with 503 when the namespace has no control plane

1 participant