GeezerVison is showing github.com — feat: add get, list and delete routes for chat automations by ThomasK33 · Pull Request #32079 · coder/coder
Conversation
ThomasK33
commented
Nov 12, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
Chat automations send a saved prompt to a chat from a webhook or a schedule (RFC: Automations for Coder Agents). This PR adds the routes that read and delete them, behind the chat-automations experiment, so that create, the scheduler and the webhook endpoint can follow in small PRs. Part of CODAGT-1135.
Stacked on #32067 and #32063 through the base branch tk/automations-base-api-read, which holds both. Review only this diff. This PR is retargeted to main when both are merged.
What changed
- Three routes under
/api/experimental/organizations/{organization}/chats/automations: list (with ?owner=me, a user ID or a username), get, and delete. A middleware answers 404 when the experiment is off for the caller, and when the experiment rules cannot be read.
- Access follows the RFC role table. A member with
agents-access reads and deletes their own automations. Organization administrators and deployment owners read and delete every automation in the organization. The list filters rows in SQL. A caller who can read an automation but not delete it gets 403 on delete. A caller who cannot read it, or who uses the wrong organization in the path, gets 404. An API key with an external scope gets 404 on get and delete, and on list either 200 [] or, when it cannot read the organization, 404.
codersdk.ChatAutomation is the response type, built by explicit converters. It has no secret field, only webhook_secret_version, and get and list never copy the stored secret. A chat title appears only when the caller can read that chat. An owner appears only when the caller can read that user, and a ?owner= username that names a user the caller cannot read gives 200 []. next_run_times holds up to five upcoming runs of an enabled schedule.
chatautomation.Delete takes the automation's advisory lock, re-reads the row as the caller, and deletes it in one transaction. It is the one function that the later API and agent tool paths call.
ChatAutomation is audited for delete. prompt and webhook_secret are masked secret fields. ResourceTypeChatAutomation loses its "retained for audit logs" comment, which is no longer true, so gofmt re-aligns the constant block.
- Two queries that the routes need:
GetChatsByIDs (chat titles for a page of rows) and GetChatIDsWithQueuedMessagesByAutomationID (the delete check), both with dbauthz checks.
Suggested reading order: coderd/chatautomations.go, coderd/x/chatd/chatautomation/delete.go, coderd/database/db2sdk/chatautomation.go, codersdk/chatautomations.go, then the tests. The rest is generated code and docs.
Scope
- Not in this PR: create, update, secret rotation, the run path, the webhook endpoint, the scheduler and the agent tool. No code writes
chat_queued_messages.automation_id yet.
- With the experiment off, which is the default, the routes answer 404 and nothing else changes. The experiment that chore!: remove the chat automations experiment #30412 removed was never in a tagged release, so a leftover
--experiments=chat-automations flag exists only on dev or dogfood deployments, and those turn these routes on.
- Two replicas on different versions during an upgrade: no case exists. A replica without this PR has none of the routes and answers 404, as the gate does. A replica ignores a stored rule for an experiment that it does not know.
Tradeoffs
- Delete refuses while a queued message names the automation. Nothing writes such a message before the publish PR, which replaces the refusal with the queue cleanup. The refusal is a generic 500 with an empty detail and the cause in a log field, and nothing is deleted.
- The list has no pagination, as in the RFC. An organization administrator's list holds every automation of the organization (members times the per-owner cap that the create PR adds), and each row carries its prompt, up to
MaxPromptBytes.
- The response has three fields beyond the RFC example:
new_chat_model_config_id, project_id and reasoning_effort, named as in the create request. The RFC says PATCH "takes the same fields as create", so an editor must be able to read them. paused_reasons is always [] until the run path exists, and there is no field for the count of skipped and refused runs: that field arrives with its writer.
- The audit action map for
ChatAutomation lists only delete. The generated audit log page lists every action in the map, and nothing emits create or write yet. Later PRs add the actions they emit.
- The RFC says "Custom roles can grant any
chat_automation action." The custom role API lets an actor grant only the permissions that the actor holds (customRoleEscalationCheck), and no built-in role holds create. This PR adds no create route and keeps the role table: a custom role with read and delete deletes, one with read only gets 403, and one with delete and without read gets 404 and deletes nothing, because the delete re-reads the row with read.
Live check
On a local enterprise build with its own PostgreSQL, two users, and the experiment on for one of them: list, get, delete, a repeat delete (404) and a delete refused by a queued message (500, nothing deleted) behaved as described, the user with the experiment off got 404 on all three routes, the delete wrote one audit entry with the prompt masked, and a canary secret and prompt appeared 0 times in the audit log, the server log and the PostgreSQL log (the secret also 0 times in every response).
Generated with xum • Model: anthropic:claude-sonnet-5-5 • Thinking: xhigh
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Mark database.ChatAutomation as an Auditable, with the target name and ID,
the organization requirement, and the audit table. webhook_secret and
prompt are ActionSecret, so an entry shows that they changed and never
their value.
…n needs
GetChatsByIDs reads the chats whose titles a response shows.
GetChatIDsWithQueuedMessagesByAutomationID and
DeleteChatQueuedMessagesByAutomationID let a delete lock the chats that hold
queued messages of the automation before it removes them.
The routes sit under /api/experimental/organizations/{organization}/chats/automations
behind the chat-automations experiment, which is evaluated for the caller and
answers 404 when off. The response type has no webhook secret field. Delete
locks the automation, then each chat that holds a queued message of it in chat
ID order like a chat transition, then removes the queued messages and the row.
ARCHITECTURE.md prose was drafted by an agent at the user's request; the user
will review it.
The request logger logs the response body of a 5xx, so the handlers write the
message only and log the error.
Cut test rows, phases and helpers that no product mutant separates,
merge the two lock-order subtests into one test, and replace the
expected-value builder of the response test with targeted assertions.
Add TestChatAutomationsInternalError: a 500 from list, get or delete
carries a message and no detail, and the response body never holds the
cause.
This PR audits only the delete. A later PR adds the action that it audits,
so that the audit log page never lists an action that nothing emits.
A role that can read a chat automation and cannot delete it got 404. The
chat project routes answer 403 in that case. Also correct the comments that
the review found false or unclear, and the ARCHITECTURE.md bullet.
…ssage names the automation
Delete no longer removes queued messages. Nothing writes automation_id on a
queued row before the run path exists, and how a delete leaves a chat's queue
is a chat state decision that belongs with that path. Delete takes the
automation lock, re-reads the automation and refuses with ErrQueuedMessages
when a queued message names it. The handler answers a generic 500.
…sages of an automation
Delete refuses a queued message instead of removing it, so nothing calls the
query. GetChatIDsWithQueuedMessagesByAutomationID stays: Delete uses it for the
refusal.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Codex Review Summary: ✅ Completed
This comment shows the latest Codex review activity on this pull request.
| Review |
Status |
Commit |
Review trigger |
| 📝 Code Review |
✅ Completed 2026-11-12T15:02:21.795768Z |
a52fa51 |
Manual request |
| 🔒 Security Review |
✅ Completed 2026-11-12T15:04:17.123719Z |
a52fa51 |
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.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
🛡️ Codex Security Review
Security review completed. No security issues were found in this pull request.
Reviewed commit: 3e06f69bb3
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.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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: 3e06f69bb3
ℹ️ 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".
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…tomation owner filter
uuid.Parse also accepts 32 hexadecimal characters, which is a valid username,
and the braces and urn:uuid: forms. The owner filter took each of them for a
user ID. It now takes the ID branch only for the 36-character form with
hyphens, and looks up every other value as a username.
Remove the blank line between the cdr.dev and github.com/coder import groups,
and read the audit log in the enterprise test as an auditor instead of the
owner.
… read
The batch read of the owners is all or nothing, so a caller who could read
some owners and not others saw no owner on any row. When the batch is not
authorized, read the owners one by one and leave out only those that the
caller cannot read.
…sts already cover
No mutant of the product code turns only this test red. The coderd lifecycle test
fails when the entry is missing, the unit tests cover the resource type, target
and masking, and the lifecycle test now also checks the organization ID.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Codex Review: Didn't find any major issues. Breezy!
Reviewed commit: a52fa51012
ℹ️ 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".
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
🛡️ Codex Security Review
Security review completed. No security issues were found in this pull request.
Reviewed commit: a52fa51012
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.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This branch has not been deployed
No deployments
Uh oh!
There was an error while loading. Please reload this page.
Milestone
No milestone
Development
Successfully merging this pull request may close these issues.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|