Skip to content

🤖 feat: sweep the cleanup records of CoderAIProvider objects that went without a delete (#353 part 2b) - #375

Merged
ThomasK33 merged 20 commits into
mainfrom
feat/ai-provider-cleanup-sweep
Oct 14, 2026
Merged

ThomasK33 merged 20 commits into
mainfrom
feat/ai-provider-cleanup-sweep

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Part 2b of #353, stacked on #374 (base feat/ai-provider-cleanup-records). A new controller sweeps the cleanup records of CoderAIProvider objects that went without a delete in Coder. After a namespace deletion with an external database, the leftover provider goes once a new control plane on the same database is ready. A different Coder deployment is never swept, also at the same URL.

Refs #353

How the sweep works

CleanupRecordReconciler (controller cleanuprecord) watches the record ConfigMaps in the operator namespace. A deleted CoderAIProvider wakes its record. A control plane that gets operator access (or a new URL) wakes every record. For one record it:

  1. Checks the object (invariant 6). It reads the CoderAIProvider live through the API reader. If an object with the record's UID exists, it does nothing and looks again in 30 minutes. A new object with the same name but another UID counts as gone.
  2. Waits for the settle window (invariant 6). It acts only 5 minutes after the record's last create attempt.
  3. Finds a matching control plane (invariant 4). It reads /api/v2/buildinfo through each ready CoderControlPlane in the object's own namespace, and uses the first whose deployment ID equals the record's. It never matches by URL or name. Coder reports its deployment ID itself, so a control plane in another namespace (which someone else can run) never sweeps a record (security review round 2). The operator access is read live. When a ready control plane does not answer yet (for example while its Coder pods start), it retries in 1 minute.
  4. Deletes only what the record proves (invariant 5). It first requires a site-owner operator user, so Coder's 404 is trustworthy. It deletes the recorded provider ID. Without a recorded ID, it reads the recorded name and deletes only a provider whose display name is still the record's nonce and whose type is the recorded type. When several control planes in the namespace match, it tries each until Coder confirms. A provider of that name without the nonce is someone else's: the record goes, and the provider stays.
  5. Removes the record only after Coder confirms (invariant 7). Success or Coder's own 404 confirms. Any other answer, including a proxy 404, keeps the record and retries in 1 minute.
  6. Reports a record that no deployment reaches (invariant 8). The record stays. The gauge coder_k8s_cleanup_records_unmatched{kind} counts it, and a NoMatchingDeployment Warning Event on the ConfigMap names the object and the deployment ID. It looks again every 10 minutes. The docs tell an admin to delete the provider by hand and then the ConfigMap.

The counter coder_k8s_cleanup_records_swept_total{kind} counts swept records. A ConfigMap that is not a valid record (wrong label, name or UID) is logged and never acted on.

RBAC. The sweep uses the namespaced Role from 2a to delete records. It adds events.k8s.io events create/patch for the Event (the new GetEventRecorder API). Coder access goes through the same operator token as every other controller.

Docs. The provider how-to "Cleanup records" section describes the sweep, the metrics, the Event and the manual way out. It keeps the warning to delete providers before their namespace: that is the only fix when no control plane ever reconnects to that database.

Dogfood

Evidence on c4281d51 (same provider and sweep code as the head, which only adds a merge of main): #375 (comment) (earlier heads: 6175120718, 6174550134, 6173286421) (Kind, Coder v2.37.2, no license, external Postgres with databases A and B).

  1. A namespace deletion leaves the provider in database A, and its record stays.
  2. A new control plane at the same URL on database B (another deployment) sweeps nothing. It raises a NoMatchingDeployment Event.
  3. A control plane in another namespace on database A sweeps nothing either.
  4. The namespace recreated with a control plane on database A sweeps the provider: database A has 0 live providers, and 0 records are left.

Validation

Local gates on the tree of this commit before a docs-only edit and a rebase onto main (1c0df24c, #373: CI and E2E script files only; no Go change came with it):

  • Passed: make build-installer, make codegen, make docs-reference, drift (git diff --exit-code), make verify-vendor, make build, make lint, govulncheck, make test, make test-integration, go test -race ./internal/controller/..., make docs-check, actionlint v1.7.10.
  • After the docs edit (one line in docs/explanation/architecture.md) and the rebase: make docs-check, actionlint and go build ./... again.

Mutation checks (each one made a test fail): no namespace restriction, live access read replaced by the cache, no UID compare, no settle window, no deployment-ID match, no nonce check, record removed on a failed delete, any 404 accepted, no Event, no site-owner check, unknown deployment treated as unmatched.

Not in this PR


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

Change-Id: Ice66d8e736e7372a3afd95d8c96a79955aca6b04
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…without a delete (#353)

Change-Id: I81ad241e8a8032f3df9eac7d825c0c70fb7da105
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: Ice66d8e736e7372a3afd95d8c96a79955aca6b04
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…without a delete (#353)

Change-Id: I81ad241e8a8032f3df9eac7d825c0c70fb7da105
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 14, 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-14T18:58:35.291547Z 283f46a Manual request
🔒 Security Review ✅ Completed 2026-10-14T18:59:23.513381Z 283f46a Manual request

Security findings

Advisory findings (1)

ℹ️ 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

ThomasK33 commented Oct 14, 2026 •

Copy link
Copy Markdown
Member Author

Dogfood: namespace deletion with an external database, then a sweep

Head of the operator image: 76d30aebff353c1391e09edf63f050de015c642d, image sha256:6dbdfdd0a1fb459b3162769de51bbdbcd727f73bc932a93826995da10276b76a (the running pod reports the same image ID). This PR's head 0c7697dd03dae2ef12edf3233bccd5794699fc7f has the same Go code. Its later commits changed one docs line and rebased onto main, and main brought no Go change.

Setup: Kind v1.32.0, Coder v2.37.2 without a license, Postgres in namespace db with database A (coder) and database B (coderb). The table on the PR body sums it up.

End state

In step 3, the second NoMatchingDeployment Event belongs to the record of my first run. I deleted that record and both databases before this run, and the Event stayed until it expired.

Transcript
head=76d30aebff353c1391e09edf63f050de015c642d image=sha256:6dbdfdd0a1fb459b3162769de51bbdbcd727f73bc932a93826995da10276b76a
### 1. Namespace ns1: control plane coder on database A, and CoderAIProvider openai  (16:27:14Z)
coderaiprovider.coder.com/openai created
CoderAIProvider ns1/openai: Ready=True providerID=ce6fe383-1a2a-43a3-aa87-a6fa3d52b8ec uid=c37c1719-4061-46ce-b70e-1be98b10020b
database A: deployment ID 8b7f592d-6e9c-49f9-a734-5cfd1c6f8b56, providers live=1 soft-deleted=0
cleanup records:
ck8s-cleanup-c37c1719-4061-46ce-b70e-1be98b10020b kind=CoderAIProvider object=ns1/openai deploymentID=8b7f592d-6e9c-49f9-a734-5cfd1c6f8b56 coderID=ce6fe383-1a2a-43a3-aa87-a6fa3d52b8ec
the record holds no key: 0 matches of sk-dogfood
### 2. Delete namespace ns1: Coder's pods go at once, so no delete reaches Coder  (16:27:32Z)
namespace "ns1" deleted
namespace ns1 gone after 11 s
operator log: 1 release(s) of the provider without a delete
database A: providers live=1 soft-deleted=0  <- the provider is left in Coder
cleanup records (the record stays):
ck8s-cleanup-c37c1719-4061-46ce-b70e-1be98b10020b kind=CoderAIProvider object=ns1/openai deploymentID=8b7f592d-6e9c-49f9-a734-5cfd1c6f8b56 coderID=ce6fe383-1a2a-43a3-aa87-a6fa3d52b8ec
### 3. Negative control: a new ns1 whose control plane has the SAME URL but uses database B (another deployment)  (16:27:44Z)
status.url of ns1/coder: http://coder.ns1.svc.cluster.local:80
deployment ID answered at that URL now: 590afa79-d7da-4f60-a539-605e6cc66873   (database A: 8b7f592d-6e9c-49f9-a734-5cfd1c6f8b56)
waiting 297 s for the end of the 5-minute settle window
events on the record:
Warning   NoMatchingDeployment   ck8s-cleanup-c37c1719-4061-46ce-b70e-1be98b10020b   No ready CoderControlPlane reaches Coder deployment 8b7f592d-6e9c-49f9-a734-5cfd1c6f8b56, which holds the provider of deleted CoderAIProvider ns1/openai. Delete the 
Warning   NoMatchingDeployment   ck8s-cleanup-e2434528-39ff-456c-a034-cbfe56ab3600   No ready CoderControlPlane reaches Coder deployment fbd1b271-dc4c-42a5-a728-ced76377bc7e, which holds the provider of deleted CoderAIProvider ns1/openai. Delete the 
database A: providers live=1 soft-deleted=0  <- not swept through the other deployment
database B: providers live=0 soft-deleted=0
cleanup records (still there):
ck8s-cleanup-c37c1719-4061-46ce-b70e-1be98b10020b kind=CoderAIProvider object=ns1/openai deploymentID=8b7f592d-6e9c-49f9-a734-5cfd1c6f8b56 coderID=ce6fe383-1a2a-43a3-aa87-a6fa3d52b8ec
coder_k8s_cleanup_records_unmatched{kind="CoderAIProvider"} 1
coder_k8s_cleanup_released_without_delete_total{kind="CoderAIProvider"} 1
### 4. Namespace ns2: a new control plane on database A again  (16:33:03Z)
deployment ID answered by ns2/coder: 8b7f592d-6e9c-49f9-a734-5cfd1c6f8b56
operator log: "controlPlane": {"name":"coder","namespace":"ns2"} "result": "deleted provider ce6fe383-1a2a-43a3-aa87-a6fa3d52b8ec" 
### 5. Result  (16:34:04Z)
database A: providers live=0 soft-deleted=1
cleanup records: 0
coder_k8s_cleanup_records_swept_total{kind="CoderAIProvider"} 1
coder_k8s_cleanup_records_unmatched{kind="CoderAIProvider"} 0
coder_k8s_cleanup_released_without_delete_total{kind="CoderAIProvider"} 1

The script never prints the provider API key. The transcript has 0 matches of the key value.

Recording:

sweep-dogfood.mp4

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

@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: 76d30aebff

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: 76d30aebff

ℹ️ 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/controller/coderaiprovider_record.go Outdated
Comment thread .github/workflows/ci.yaml Outdated
Comment thread internal/controller/coderaiprovider_controller.go
…lete only a valid record, and use the recorded provider ID on delete

Change-Id: I43013d332be2b96542173c7871a73bdf8efe4957
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: I2416199a0419ac66fee5991058b8158b841ea2e7
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: Id47fff062cc502fe08acd8a4b7ae52512512480d
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: I53d93aa17648495174eaea99b89f62cb44c830ed
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@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: d80f29e1fe

ℹ️ 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/controller/cleanup_sweep.go Outdated
Comment thread internal/controller/cleanup_sweep.go Outdated

@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 Security Review

Here are some automated security review suggestions for this pull request.

Reviewed commit: d80f29e1fe

ℹ️ 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.

Comment thread internal/controller/cleanup_sweep.go Outdated
…ntrol plane reaches another deployment, and guard the setup

Change-Id: Iee4add631b870c6070066c275f89f84ed86762fa
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…-cleanup-sweep

Change-Id: I2b8212f8353ef7511de29ca2da53bbd68bd3aa60
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…ntrol plane without working access as unknown

Change-Id: I14ec8db9add5723cde4a6f4f1b3308a1afb1f82c
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…space

Change-Id: I0ce5b4e4ac65226e910f22ff8055a9100069f69e
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@ThomasK33

ThomasK33 commented Oct 14, 2026 •

Copy link
Copy Markdown
Member Author

Dogfood on head 0884db3 (after review rounds 1 and 2)

This replaces the run in the comment above, which used an earlier head. Operator image sha256:c5092c1d635dfe824ee2d93d9771548fa09ad8733c215ecd1481b6969cd310a9, built from 0884db30. The running pod reports the same image ID. Same cluster and databases (A = coder, B = coderb). I emptied both databases and removed the records before the run.

Step Result
1. ns1: control plane on database A, plus CoderAIProvider openai Ready. The record names deployment 2512e51d… and the provider ID. No key in the record.
2. Delete namespace ns1 Gone in 11 s. 1 release without a delete. The provider stays in database A (live=1), and the record stays.
3. A new ns1 with the same URL (http://coder.ns1.svc.cluster.local:80) on database B That URL answers deployment 670b2c52…. After the settle window: a NoMatchingDeployment Event, unmatched gauge 1, no delete.
4. Spoof control: ns2 gets a control plane on database A, so it answers the record's deployment ID No sweep after 90 s: live=1, 1 record. Only a control plane in the object's own namespace counts (fix for thread 4273916385).
5. ns1 comes back with a control plane on database A Swept through ns1/coder: "deleted provider 672977a8…".
6. Result Database A: live=0, soft-deleted=1. 0 records.

The operator ran through the earlier runs too, so its counters add up across runs: swept_total reads 1 before this run and 2 after it. The extra NoMatchingDeployment Events in step 3 belong to records of the earlier runs, which I deleted before this run.

End state

An earlier run on this head showed namespace ns1 being terminated in the middle of the run, and the sweep then ran through ns1 at that moment. I could not find the cause in the logs. For this run I added a watch of the namespaces (below) and a check that each namespace deletion is complete. The watch shows ns1 recreated only in step 3 and step 5, and the result matches the design.

Namespace watch (ns1, ns2)
17:30:41 ns1                  Active   2026-10-14T17:30:41Z   <none>
17:31:00 ns1                  Terminating   2026-10-14T17:30:41Z   2026-10-14T17:31:00Z
17:31:06 ns1                  Terminating   2026-10-14T17:30:41Z   2026-10-14T17:31:00Z
17:31:11 ns1                  Terminating   2026-10-14T17:30:41Z   2026-10-14T17:31:00Z
17:31:11 ns1                  Terminating   2026-10-14T17:30:41Z   2026-10-14T17:31:00Z
17:31:12 ns1                  Active        2026-10-14T17:31:12Z   <none>
17:36:31 ns2                  Active        2026-10-14T17:36:31Z   <none>
17:38:16 ns2                  Terminating   2026-10-14T17:36:31Z   2026-10-14T17:38:16Z
17:38:21 ns2                  Terminating   2026-10-14T17:36:31Z   2026-10-14T17:38:16Z
17:38:26 ns2                  Terminating   2026-10-14T17:36:31Z   2026-10-14T17:38:16Z
17:38:26 ns2                  Terminating   2026-10-14T17:36:31Z   2026-10-14T17:38:16Z
17:38:27 ns1                  Terminating   2026-10-14T17:31:12Z   2026-10-14T17:38:27Z
17:38:32 ns1                  Terminating   2026-10-14T17:31:12Z   2026-10-14T17:38:27Z
17:38:37 ns1                  Terminating   2026-10-14T17:31:12Z   2026-10-14T17:38:27Z
17:38:37 ns1                  Terminating   2026-10-14T17:31:12Z   2026-10-14T17:38:27Z
17:38:37 ns1                  Active        2026-10-14T17:38:37Z   <none>
Transcript
head=0884db303783757ae8739bfe523e8e387bb2d2c8 image=sha256:c5092c1d635dfe824ee2d93d9771548fa09ad8733c215ecd1481b6969cd310a9
### 1. Namespace ns1: control plane coder on database A, and CoderAIProvider openai  (17:30:41Z)
coderaiprovider.coder.com/openai created
CoderAIProvider ns1/openai: Ready=True providerID=672977a8-d0c8-43ff-a2cb-aab1b104fc6e uid=41deea00-617b-41a5-8c65-b7c97bb5a811
database A: deployment ID 2512e51d-9afc-4eec-a585-01784c1f4b55, providers live=1 soft-deleted=0
cleanup records:
ck8s-cleanup-41deea00-617b-41a5-8c65-b7c97bb5a811 kind=CoderAIProvider object=ns1/openai deploymentID=2512e51d-9afc-4eec-a585-01784c1f4b55 coderID=672977a8-d0c8-43ff-a2cb-aab1b104fc6e
the record holds no key: 0 matches of sk-dogfood
### 2. Delete namespace ns1: Coder's pods go at once, so no delete reaches Coder  (17:31:00Z)
namespace "ns1" deleted
namespace ns1 gone after 11 s
operator log: 1 release(s) of the provider without a delete
database A: providers live=1 soft-deleted=0  <- the provider is left in Coder
cleanup records (the record stays):
ck8s-cleanup-41deea00-617b-41a5-8c65-b7c97bb5a811 kind=CoderAIProvider object=ns1/openai deploymentID=2512e51d-9afc-4eec-a585-01784c1f4b55 coderID=672977a8-d0c8-43ff-a2cb-aab1b104fc6e
### 3. Negative control: a new ns1 whose control plane has the SAME URL but uses database B (another deployment)  (17:31:12Z)
control plane ns1/coder: c46df824-344f-47d6-83de-449e7f2fb6e6 created=2026-10-14T17:31:12Z db=coder-db secret-db=coderb
status.url of ns1/coder: http://coder.ns1.svc.cluster.local:80
deployment ID answered at that URL now: 670b2c52-498b-497c-a418-e828f57fa052   (database A: 2512e51d-9afc-4eec-a585-01784c1f4b55)
waiting 287 s for the end of the 5-minute settle window
events on the record:
Warning   NoMatchingDeployment   ck8s-cleanup-41deea00-617b-41a5-8c65-b7c97bb5a811   No ready CoderControlPlane in namespace ns1 reaches Coder deployment 2512e51d-9afc-4eec-a585-01784c1f4b55, which holds the provider of deleted CoderAIProvider ns1/op
Warning   NoMatchingDeployment   ck8s-cleanup-8e9019c9-eccd-4910-a06f-6704bc6026ed   No ready CoderControlPlane reaches Coder deployment bc5980cc-d78b-44a8-8906-c70edc2c140e, which holds the provider of deleted CoderAIProvider ns1/openai. Delete the 
Warning   NoMatchingDeployment   ck8s-cleanup-bef5e500-041b-4993-9aa0-6e71d28b1d49   No ready CoderControlPlane in namespace ns1 reaches Coder deployment e9a8791c-1920-44be-ad8d-feeb7638e952, which holds the provider of deleted CoderAIProvider ns1/op
database A: providers live=1 soft-deleted=0  <- not swept through the other deployment
database B: providers live=0 soft-deleted=0
cleanup records (still there):
ck8s-cleanup-41deea00-617b-41a5-8c65-b7c97bb5a811 kind=CoderAIProvider object=ns1/openai deploymentID=2512e51d-9afc-4eec-a585-01784c1f4b55 coderID=672977a8-d0c8-43ff-a2cb-aab1b104fc6e
coder_k8s_cleanup_records_swept_total{kind="CoderAIProvider"} 1
coder_k8s_cleanup_records_unmatched{kind="CoderAIProvider"} 1
coder_k8s_cleanup_released_without_delete_total{kind="CoderAIProvider"} 2
### 4. Spoof control: namespace ns2 gets a control plane on database A (it answers the record's deployment ID)  (17:36:31Z)
deployment ID answered by ns2/coder: 2512e51d-9afc-4eec-a585-01784c1f4b55
control plane ns1/coder now: c46df824-344f-47d6-83de-449e7f2fb6e6 created=2026-10-14T17:31:12Z db=coder-db secret-db=coderb
database A after 90 s: providers live=1 soft-deleted=0  <- a control plane in another namespace never sweeps
cleanup records: 1
### 5. Namespace ns1 comes back with a control plane on database A  (17:38:27Z)
deployment ID answered by ns1/coder: 2512e51d-9afc-4eec-a585-01784c1f4b55
operator log: "controlPlane": {"name":"coder","namespace":"ns1"} "result": "deleted provider 672977a8-d0c8-43ff-a2cb-aab1b104fc6e" 
### 6. Result  (17:39:40Z)
database A: providers live=0 soft-deleted=1
cleanup records: 0
coder_k8s_cleanup_records_swept_total{kind="CoderAIProvider"} 2
coder_k8s_cleanup_records_unmatched{kind="CoderAIProvider"} 0
coder_k8s_cleanup_released_without_delete_total{kind="CoderAIProvider"} 2

The script never prints the provider API key: 0 matches in the transcript.

Recording:

sweep-dogfood-2.mp4

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

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

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

Reviewed commit: 0884db3037

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: 0884db3037

ℹ️ 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/controller/cleanup_sweep.go Outdated
Comment thread internal/controller/cleanup_sweep.go Outdated
… record the provider type, and default the record namespace to coder-system

Change-Id: I7b699d65437effb2e94cefb175ee758d163b2c83
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…-cleanup-sweep

Change-Id: Ia86133255959b1668ffd6e0a77689f08f6341b37
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…d check the provider type on the nonce path

Change-Id: I515ed4a2e1d079f0b6553532947672482b786ffe
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@ThomasK33

ThomasK33 commented Oct 14, 2026 •

Copy link
Copy Markdown
Member Author

Dogfood on the final head 5e9e849 (after review round 3)

Operator image sha256:f8523e4ce278c00cfcaf0d0a751ce1251098f4ff7a40cfc6213b630fdddb8dcf, built from 5e9e8494, freshly rolled out, so its counters start at 0. I emptied databases A (coder) and B (coderb) and removed all records before the run.

Step Result
1. ns1 on database A, plus CoderAIProvider openai Ready. The record names deployment c248ed1e….
2. Delete namespace ns1 Gone in 11 s. The provider stays in database A (live=1), and the record stays.
3. A new ns1 at the same URL on database B The URL answers 959f12e5…. A NoMatchingDeployment Event, unmatched 1, no delete.
4. ns2 on database A (it answers the record's deployment ID) No sweep after 90 s: live=1, 1 record.
5. ns1 comes back on database A Swept through ns1/coder: "deleted provider 58326629…".
6. Result Database A: live=0, soft-deleted=1. 0 records. swept_total 1, unmatched 0.

The namespace watch shows each namespace created and deleted only by the script steps.

Namespace watch (ns1, ns2)
17:57:02 ns1                  Active   2026-10-14T17:57:02Z   <none>
17:57:20 ns1                  Terminating   2026-10-14T17:57:02Z   2026-10-14T17:57:20Z
17:57:26 ns1                  Terminating   2026-10-14T17:57:02Z   2026-10-14T17:57:20Z
17:57:31 ns1                  Terminating   2026-10-14T17:57:02Z   2026-10-14T17:57:20Z
17:57:31 ns1                  Terminating   2026-10-14T17:57:02Z   2026-10-14T17:57:20Z
17:57:32 ns1                  Active        2026-10-14T17:57:32Z   <none>
18:02:51 ns2                  Active        2026-10-14T18:02:51Z   <none>
18:04:31 ns2                  Terminating   2026-10-14T18:02:51Z   2026-10-14T18:04:31Z
18:04:36 ns2                  Terminating   2026-10-14T18:02:51Z   2026-10-14T18:04:31Z
18:04:41 ns2                  Terminating   2026-10-14T18:02:51Z   2026-10-14T18:04:31Z
18:04:41 ns2                  Terminating   2026-10-14T18:02:51Z   2026-10-14T18:04:31Z
18:04:42 ns1                  Terminating   2026-10-14T17:57:32Z   2026-10-14T18:04:42Z
18:04:48 ns1                  Terminating   2026-10-14T17:57:32Z   2026-10-14T18:04:42Z
18:04:53 ns1                  Terminating   2026-10-14T17:57:32Z   2026-10-14T18:04:42Z
18:04:53 ns1                  Terminating   2026-10-14T17:57:32Z   2026-10-14T18:04:42Z
18:04:53 ns1                  Active        2026-10-14T18:04:53Z   <none>
Transcript
head=5e9e8494e88203862072d8a7ec357dbe87e5246c image=sha256:f8523e4ce278c00cfcaf0d0a751ce1251098f4ff7a40cfc6213b630fdddb8dcf
### 1. Namespace ns1: control plane coder on database A, and CoderAIProvider openai  (17:57:01Z)
coderaiprovider.coder.com/openai created
CoderAIProvider ns1/openai: Ready=True providerID=58326629-b2f8-472c-84d7-388f09c44331 uid=4fc01f6d-713b-48ba-8986-99b6498e1c73
database A: deployment ID c248ed1e-59e4-4f9c-9379-ffd8f9c90606, providers live=1 soft-deleted=0
cleanup records:
ck8s-cleanup-4fc01f6d-713b-48ba-8986-99b6498e1c73 kind=CoderAIProvider object=ns1/openai deploymentID=c248ed1e-59e4-4f9c-9379-ffd8f9c90606 coderID=58326629-b2f8-472c-84d7-388f09c44331
the record holds no key: 0 matches of sk-dogfood
### 2. Delete namespace ns1: Coder's pods go at once, so no delete reaches Coder  (17:57:20Z)
namespace "ns1" deleted
namespace ns1 gone after 11 s
operator log: 1 release(s) of the provider without a delete
database A: providers live=1 soft-deleted=0  <- the provider is left in Coder
cleanup records (the record stays):
ck8s-cleanup-4fc01f6d-713b-48ba-8986-99b6498e1c73 kind=CoderAIProvider object=ns1/openai deploymentID=c248ed1e-59e4-4f9c-9379-ffd8f9c90606 coderID=58326629-b2f8-472c-84d7-388f09c44331
### 3. Negative control: a new ns1 whose control plane has the SAME URL but uses database B (another deployment)  (17:57:32Z)
control plane ns1/coder: 85a3acec-c7f9-49df-b65a-f55776e9249b created=2026-10-14T17:57:32Z db=coder-db secret-db=coderb
status.url of ns1/coder: http://coder.ns1.svc.cluster.local:80
deployment ID answered at that URL now: 959f12e5-1b57-4a6f-a1fe-e2a5810bc513   (database A: c248ed1e-59e4-4f9c-9379-ffd8f9c90606)
waiting 297 s for the end of the 5-minute settle window
events on the record:
Warning   NoMatchingDeployment   ck8s-cleanup-41deea00-617b-41a5-8c65-b7c97bb5a811   No ready CoderControlPlane in namespace ns1 reaches Coder deployment 2512e51d-9afc-4eec-a585-01784c1f4b55, which holds the provider of deleted CoderAIProvider ns1/op
Warning   NoMatchingDeployment   ck8s-cleanup-4fc01f6d-713b-48ba-8986-99b6498e1c73   No ready CoderControlPlane in namespace ns1 reaches Coder deployment c248ed1e-59e4-4f9c-9379-ffd8f9c90606, which holds the provider of deleted CoderAIProvider ns1/op
Warning   NoMatchingDeployment   ck8s-cleanup-8e9019c9-eccd-4910-a06f-6704bc6026ed   No ready CoderControlPlane reaches Coder deployment bc5980cc-d78b-44a8-8906-c70edc2c140e, which holds the provider of deleted CoderAIProvider ns1/openai. Delete the 
Warning   NoMatchingDeployment   ck8s-cleanup-bef5e500-041b-4993-9aa0-6e71d28b1d49   No ready CoderControlPlane in namespace ns1 reaches Coder deployment e9a8791c-1920-44be-ad8d-feeb7638e952, which holds the provider of deleted CoderAIProvider ns1/op
database A: providers live=1 soft-deleted=0  <- not swept through the other deployment
database B: providers live=0 soft-deleted=0
cleanup records (still there):
ck8s-cleanup-4fc01f6d-713b-48ba-8986-99b6498e1c73 kind=CoderAIProvider object=ns1/openai deploymentID=c248ed1e-59e4-4f9c-9379-ffd8f9c90606 coderID=58326629-b2f8-472c-84d7-388f09c44331
coder_k8s_cleanup_records_unmatched{kind="CoderAIProvider"} 1
coder_k8s_cleanup_released_without_delete_total{kind="CoderAIProvider"} 1
### 4. Spoof control: namespace ns2 gets a control plane on database A (it answers the record's deployment ID)  (18:02:51Z)
deployment ID answered by ns2/coder: c248ed1e-59e4-4f9c-9379-ffd8f9c90606
control plane ns1/coder now: 85a3acec-c7f9-49df-b65a-f55776e9249b created=2026-10-14T17:57:32Z db=coder-db secret-db=coderb
database A after 90 s: providers live=1 soft-deleted=0  <- a control plane in another namespace never sweeps
cleanup records: 1
### 5. Namespace ns1 comes back with a control plane on database A  (18:04:42Z)
deployment ID answered by ns1/coder: c248ed1e-59e4-4f9c-9379-ffd8f9c90606
operator log: "controlPlane": {"name":"coder","namespace":"ns1"} "result": "deleted provider 58326629-b2f8-472c-84d7-388f09c44331" 
### 6. Result  (18:05:55Z)
database A: providers live=0 soft-deleted=1
cleanup records: 0
coder_k8s_cleanup_records_swept_total{kind="CoderAIProvider"} 1
coder_k8s_cleanup_records_unmatched{kind="CoderAIProvider"} 0
coder_k8s_cleanup_released_without_delete_total{kind="CoderAIProvider"} 1

The transcript has 0 matches of the provider API key.

Recording:

sweep-dogfood-3.mp4

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

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: 5e9e8494e8

ℹ️ 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".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

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

Reviewed commit: 5e9e8494e8

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.

… an empty status

Change-Id: Ib7fa8097dbaf483d8f50c72676c746ceceabc1c1
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…-cleanup-sweep

Change-Id: Id144dbfc8aa981a1b9aff68e89e961198c550910
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…nup-records

Change-Id: I391ceb4bb8f88ccfa3c5f8a6d3601d9f3b654557
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…-cleanup-sweep

Change-Id: I22f854ac36863e05bcd045adf62f1c7d6702125a
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@ThomasK33

ThomasK33 commented Oct 14, 2026 •

Copy link
Copy Markdown
Member Author

Dogfood on c4281d5 (after the round-4 blocker fix)

Operator image sha256:a9e0398ba7b3cd9683bd5fd2c9ceaa71ab61feadf321544f1cd1a12de1020ce6, built from c4281d51, freshly rolled out. The PR head 283f46a0 only adds a merge of main (5cfffaa8, CoderMCPServer activation and RBAC tests). git diff c4281d51 283f46a0 touches no coderaiprovider*, cleanup_* or controlplane_* file. The CI Kind E2E runs on the exact head once #374 lands and this PR targets main.

The results match the run in 6175120718:

  1. Namespace deletion: the provider is left in database A, and the record stays.
  2. Same URL on database B: NoMatchingDeployment, no delete.
  3. ns2 on database A: no sweep after 90 s.
  4. Recreated ns1 on database A: swept through ns1/coder.
  5. Final state: database A live=0 / soft-deleted=1, 0 records, swept_total 1, unmatched 0.
Namespace watch
18:24:42 ns1                  Active   2026-10-14T18:24:42Z   <none>
18:25:01 ns1                  Terminating   2026-10-14T18:24:42Z   2026-10-14T18:25:01Z
18:25:06 ns1                  Terminating   2026-10-14T18:24:42Z   2026-10-14T18:25:01Z
18:25:11 ns1                  Terminating   2026-10-14T18:24:42Z   2026-10-14T18:25:01Z
18:25:11 ns1                  Terminating   2026-10-14T18:24:42Z   2026-10-14T18:25:01Z
18:25:12 ns1                  Active        2026-10-14T18:25:12Z   <none>
18:30:32 ns2                  Active        2026-10-14T18:30:32Z   <none>
18:32:22 ns2                  Terminating   2026-10-14T18:30:32Z   2026-10-14T18:32:22Z
18:32:28 ns2                  Terminating   2026-10-14T18:30:32Z   2026-10-14T18:32:22Z
18:32:33 ns2                  Terminating   2026-10-14T18:30:32Z   2026-10-14T18:32:22Z
18:32:33 ns2                  Terminating   2026-10-14T18:30:32Z   2026-10-14T18:32:22Z
18:32:33 ns1                  Terminating   2026-10-14T18:25:12Z   2026-10-14T18:32:33Z
18:32:39 ns1                  Terminating   2026-10-14T18:25:12Z   2026-10-14T18:32:33Z
18:32:44 ns1                  Terminating   2026-10-14T18:25:12Z   2026-10-14T18:32:33Z
18:32:44 ns1                  Terminating   2026-10-14T18:25:12Z   2026-10-14T18:32:33Z
18:32:44 ns1                  Active        2026-10-14T18:32:44Z   <none>
Transcript
head=c4281d519de72df6a6923457d95218d7bd52ce77 image=sha256:a9e0398ba7b3cd9683bd5fd2c9ceaa71ab61feadf321544f1cd1a12de1020ce6
### 1. Namespace ns1: control plane coder on database A, and CoderAIProvider openai  (18:24:42Z)
coderaiprovider.coder.com/openai created
CoderAIProvider ns1/openai: Ready=True providerID=789984f5-4849-46c1-8776-da6f0bb62175 uid=61fd817b-b8e3-45f1-b28d-a9afd7804560
database A: deployment ID 46ac547b-a18e-4cce-8d3e-e19f8e3976af, providers live=1 soft-deleted=0
cleanup records:
ck8s-cleanup-61fd817b-b8e3-45f1-b28d-a9afd7804560 kind=CoderAIProvider object=ns1/openai deploymentID=46ac547b-a18e-4cce-8d3e-e19f8e3976af coderID=789984f5-4849-46c1-8776-da6f0bb62175
the record holds no key: 0 matches of sk-dogfood
### 2. Delete namespace ns1: Coder's pods go at once, so no delete reaches Coder  (18:25:01Z)
namespace "ns1" deleted
namespace ns1 gone after 11 s
operator log: 1 release(s) of the provider without a delete
database A: providers live=1 soft-deleted=0  <- the provider is left in Coder
cleanup records (the record stays):
ck8s-cleanup-61fd817b-b8e3-45f1-b28d-a9afd7804560 kind=CoderAIProvider object=ns1/openai deploymentID=46ac547b-a18e-4cce-8d3e-e19f8e3976af coderID=789984f5-4849-46c1-8776-da6f0bb62175
### 3. Negative control: a new ns1 whose control plane has the SAME URL but uses database B (another deployment)  (18:25:12Z)
control plane ns1/coder: 9f1b74f6-2d28-4f8f-98d1-03e71a4041e9 created=2026-10-14T18:25:13Z db=coder-db secret-db=coderb
status.url of ns1/coder: http://coder.ns1.svc.cluster.local:80
deployment ID answered at that URL now: 6c128e57-785d-4701-869f-5d4f9444f1f6   (database A: 46ac547b-a18e-4cce-8d3e-e19f8e3976af)
waiting 284 s for the end of the 5-minute settle window
events on the record:
Warning   NoMatchingDeployment   ck8s-cleanup-41deea00-617b-41a5-8c65-b7c97bb5a811   No ready CoderControlPlane in namespace ns1 reaches Coder deployment 2512e51d-9afc-4eec-a585-01784c1f4b55, which holds the provider of deleted CoderAIProvider ns1/op
Warning   NoMatchingDeployment   ck8s-cleanup-4fc01f6d-713b-48ba-8986-99b6498e1c73   No ready CoderControlPlane in namespace ns1 reaches Coder deployment c248ed1e-59e4-4f9c-9379-ffd8f9c90606, which holds the provider of deleted CoderAIProvider ns1/op
Warning   NoMatchingDeployment   ck8s-cleanup-61fd817b-b8e3-45f1-b28d-a9afd7804560   No ready CoderControlPlane in namespace ns1 reaches Coder deployment 46ac547b-a18e-4cce-8d3e-e19f8e3976af, which holds the provider of deleted CoderAIProvider ns1/op
database A: providers live=1 soft-deleted=0  <- not swept through the other deployment
database B: providers live=0 soft-deleted=0
cleanup records (still there):
ck8s-cleanup-61fd817b-b8e3-45f1-b28d-a9afd7804560 kind=CoderAIProvider object=ns1/openai deploymentID=46ac547b-a18e-4cce-8d3e-e19f8e3976af coderID=789984f5-4849-46c1-8776-da6f0bb62175
coder_k8s_cleanup_records_unmatched{kind="CoderAIProvider"} 1
coder_k8s_cleanup_released_without_delete_total{kind="CoderAIProvider"} 1
### 4. Spoof control: namespace ns2 gets a control plane on database A (it answers the record's deployment ID)  (18:30:32Z)
deployment ID answered by ns2/coder: 46ac547b-a18e-4cce-8d3e-e19f8e3976af
control plane ns1/coder now: 9f1b74f6-2d28-4f8f-98d1-03e71a4041e9 created=2026-10-14T18:25:13Z db=coder-db secret-db=coderb
database A after 90 s: providers live=1 soft-deleted=0  <- a control plane in another namespace never sweeps
cleanup records: 1
### 5. Namespace ns1 comes back with a control plane on database A  (18:32:33Z)
deployment ID answered by ns1/coder: 46ac547b-a18e-4cce-8d3e-e19f8e3976af
46ac547b-a18e-4cce-8d3e-e19f8e3976af
operator log: "controlPlane": {"name":"coder","namespace":"ns1"} "result": "deleted provider 789984f5-4849-46c1-8776-da6f0bb62175" 
### 6. Result  (18:33:45Z)
database A: providers live=0 soft-deleted=1
cleanup records: 0
coder_k8s_cleanup_records_swept_total{kind="CoderAIProvider"} 1
coder_k8s_cleanup_records_unmatched{kind="CoderAIProvider"} 0
coder_k8s_cleanup_released_without_delete_total{kind="CoderAIProvider"} 1

0 matches of the provider API key in the transcript.

Recording:

sweep-dogfood-4.mp4

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

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: 283f46a035

ℹ️ 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".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

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

Reviewed commit: 283f46a035

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.

…nup-sweep

Change-Id: Ibf55dfa736b227d368c8038ccbfa2c7c02863278
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33
ThomasK33 added this pull request to the merge queue Oct 14, 2026
Merged via the queue into main with commit d56051f Oct 14, 2026
13 checks passed
@ThomasK33
ThomasK33 deleted the feat/ai-provider-cleanup-sweep branch October 14, 2026 21:07
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.

1 participant