GeezerVison is showing github.com — feat: add the chat automations schema and RBAC resource by ThomasK33 · Pull Request #32067 · coder/coder
Conversation
ThomasK33
commented
Nov 12, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
Why
Chat automations let a webhook or a schedule send a saved prompt to a chat as the automation's owner (RFC: Automations for Coder Agents). This is the first backend PR. It adds the database schema and the access rules, so that the API, the scheduler and the webhook endpoint can follow in small PRs behind the chat-automations experiment.
Part of CODAGT-1135.
What changed
- Migration
000642_chat_automations adds the chat_automations table with five enum types, shape checks, and a trigger that rejects a target chat, creating chat or project from another organization. It adds automation_id, input_id and prompt_hooks_pending to chat_messages and chat_queued_messages, automation_id and manage_automations_enabled to chats and chats_expanded, and five chat_automation:* values to api_key_scope.
- The organization-scoped RBAC resource
chat_automation has the actions create, read, update and delete. agents-access grants all four on a member's own automations. Organization admins and deployment owners get read, update and delete, and no create. The chatd subject gets read and update. The five scopes are internal-only, so no scope that a user can request for an API key reaches an automation.
- Queries with dbauthz checks: insert, get, list by organization (RBAC as an SQL filter), count by owner, and delete. The list leaves out
webhook_secret and its key ID. The get returns the full row with the secret, because an update needs the stored secret on both sides of its audit diff and webhook verification reads it. dbauthz rejects an insert whose owner is not the actor.
- A token's allow list cannot name
chat_automation yet. During a rolling upgrade, a replica of the previous release fails to load an API key whose allow list names a resource type it does not know. A later PR lifts this guard.
Suggested reading order: 000642_chat_automations.up.sql, queries/chatautomations.sql, dbauthz/dbauthz.go, rbac/roles.go, rbac/policy/policy.go, then the tests. The rest of the 47 files is mostly generated code and tests.
Scope
- No handler, tool or background job uses the new table or the new columns yet. The chat queries return the two new
chats columns, and nothing acts on them.
- Not in this PR: the API and SDK types, the audit log entry for automations (it lands with the first handler that audits one), encryption of the webhook secret at rest, the scheduler and the webhook endpoint. Until the encryption PR, the dbcrypt store refuses an insert with a webhook secret when encryption keys are configured, so no deployment with encryption keys can store one in plaintext.
- Visible on main after this PR: the custom role editor and the role listings show
chat_automation, because they list every RBAC resource. The audit log documentation gains two Chat fields.
Tradeoffs
- The migration deviates from the RFC SQL in three places. The RFC says "Automation owners also see the number of skipped and refused runs since the last acceptance. Acceptance resets this count to zero.", and its SQL has no column for that count, so the migration adds
last_run_unaccepted_count. owner_id has ON DELETE CASCADE, but Coder soft-deletes users, so the cascade never fires, and the automation of a deleted user would keep its webhook secret. The migration adds chat_automations to delete_deleted_user_resources(), as main does for user secrets, AI provider keys and skills. The column comment says that a queued message's input_id is set together with automation_id. The RFC SQL has no constraint for that, so the migration adds a CHECK on chat_queued_messages, as migration 000609 did.
- 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 the RFC role table gives administrators no create. This PR keeps the RFC role table. A custom role can grant read, update and delete, and nobody can grant create that way. The RFC needs a decision on that sentence.
Migration
Note The migration takes ACCESS EXCLUSIVE locks on chats, chat_messages and chat_queued_messages, and SHARE ROW EXCLUSIVE locks on organizations, users, chat_projects, chat_model_configs and dbcrypt_keys. It holds them until the migration transaction commits. During that time chat requests wait, and so do writes to the other five tables. Reads of those five tables continue.
- Locks: the migration takes every lock first, in a retry loop with a 100 ms lock timeout, as migration 000609 did (shipped in v2.39.0). It does not hold a chat table while it waits behind a long transaction. After 2 minutes without success it fails and changes nothing.
- Duration: it adds the columns without a table rewrite and builds three partial indexes. Each index build reads its whole table. On a test database with 100,000 chats and 1,000,000 messages (a 326 MB
chat_messages table, warm cache) the whole migration took 0.40 s, and the slowest statement was the chat_messages index at 116 ms. On a large deployment the time follows the size of chat_messages and the disk speed.
- Rolling upgrade: a replica on the previous release keeps working while the new release migrates, and afterwards on the new schema. Its queries name their columns, and every new column is nullable or has a default. The live check below ran this.
Rollback
Coder does not support rollbacks (docs/install/operate/upgrade.md), so the down migration serves the development server's --db-rollback flag and tests. It is still written to be safe on real data:
- Stop every replica of this release first. A replica that still serves after the down fails its own chat reads, because
chats_expanded no longer has the new columns, and it can store a custom role that names chat_automation again.
- The down refuses to run while a message in
chat_messages waits for its prompt hooks, because the previous release would start that turn without them. Its error message says what to do, and a refused down changes nothing. The check reads chat_messages once while the down holds its table locks. No code sets that flag in this PR, so the down cannot refuse yet.
- The down deletes the queued messages that an automation queued and those that still wait for their prompt hooks, removes
chat_automation from custom roles and from API key scopes and allow lists, and deletes a key that is left with no scope or with an empty allow list. The previous release then loads the remaining keys and roles as it did before the upgrade.
Live check
A rolling upgrade on a local deployment with the experiment off: a server on main kept answering API requests while a server on this head migrated the same database (162 requests, 0 failed), and it kept queueing messages on the new schema afterward. Up, down and up again left the schema byte-equal to a fresh one at each step.
Generated with xum • Model: anthropic:claude-fable-5-2 • Thinking: xhigh
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
chatgpt-codex-connector[bot] reacted with thumbs up emoji
Chat automations get one organization-scoped resource with create, read,
update and delete. The owner of an automation holds all four through
agents-access. Organization admins and deployment owners hold read, update
and delete, so nobody can create an automation that runs as another user.
The five API key scopes stay internal-only. No scope that a user can
request covers the resource.
Part of CODAGT-1135
---
_Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-fable-5-2` • Thinking: `xhigh`_
One migration adds the chat_automations table with its five enums, shape
checks and the trigger that keeps a target chat, a creating chat and a
project in the automation's organization. It also adds the provenance
columns and the prompt_hooks_pending flag on the chat tables, and the five
internal API key scopes. The migration takes its table locks first with
short, retried waits, as 000609 does.
dbauthz covers the five new queries. Only the owner inserts an automation,
and the list never loads a webhook secret. The audit table masks the
webhook secret and the prompt.
Nothing calls the new queries yet.
Part of CODAGT-1135
---
_Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-fable-5-2` • Thinking: `xhigh`_
…sion 616
The test upgraded to the latest version and then asserted that the
chat automations schema is gone. Migration 642 adds that schema again,
so the test now upgrades to 616 through the production runner, and
checks at the end that the later migrations keep the cleaned data.
Coder soft-deletes users, so the cascade on chat_automations.owner_id
never fires. Migration 642 now adds the table to the user cleanup
trigger, next to user secrets and AI provider keys, and the down
restores the trigger.
Also from the pre-PR review:
- The chatd create case now asks RBAC directly, because the insert
stops at the owner check first.
- The chat column test reads back every query with an inline column
list and both hand-written scans.
- The organization trigger test covers the creating chat on a change
of the organization alone.
- Parallel subtests use their own test handle and context.
- Comments on the chatd grant, the administrator actions, the audit
resource type and the down migration state what the code does.
golangci-lint with the normal config reported G202 for two test
tables that built an UPDATE from a column fragment, and G101 for two
fixture strings.
Delete comments that restate the code, narrate the migration or explain
our own design. Keep PostgreSQL constraints, nolint reasons, exported doc
comments and every comment that sqlc or a stored function body carries.
Signed-off-by: Thomas Kosiewski <[email protected]>
The previous commit removed every comment that the branch added. This
commit puts back the ones that give a constraint or a reason the code
cannot show, and leaves out the ones that restate a name or the next
line.
…audits
Nothing in this branch creates an audit entry for a chat automation,
and the generated audit log page listed the resource as audited. The
audit table keeps only the two new chat fields. The removed mapping
and its tests go into the pull request that adds the first audited
handler.
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-12T04:21:39.660160Z |
bc579c4 |
Manual request |
| 🔒 Security Review |
✅ Completed 2026-11-12T04:23:05.894226Z |
bc579c4 |
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: 97a37edc7f
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: 97a37edc7f
ℹ️ 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.
…evious release
The down left two kinds of rows that a binary without chat automations
handles wrongly.
It kept custom role permissions and API key allow list entries for
chat_automation. The previous release cannot load them. The down now
removes them, as migration 000616 did, and revokes a key whose allow
list would be empty.
It kept a chat_messages row whose prompt hooks have not run and dropped
the flag that says so. The previous release would start that turn
without the hooks. The down now stops with an error while such a row
exists.
…ollback path
The up migration's lock block, its 100 ms lock timeout and the
per-reference check of the organization trigger had no test that fails
when they are removed. Add tests that queue a message and update users
after the lock block, that read chats_expanded while the up waits behind
an open transaction, and that delete an automation's target chat and
creating chat in one statement.
The refused down now tells the operator to keep this release running
until chatd has started the pending turns. It no longer suggests
deleting messages. The test asserts the message, the hint, and that a
refused down leaves the schema and the rows unchanged. It also asserts
that no API key or custom role names chat_automation after the down.
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. Already looking forward to the next diff.
Reviewed commit: 3f5b030448
ℹ️ 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: 3f5b030448
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.
…642 down's error
No caller prints a PostgreSQL HINT, so the refused down now says in its
message that the operator keeps this release running until chatd has
started the pending turns.
Add tests for two protections of the down that no test covered. The
lock block takes custom_roles, so the down does not hold the chat tables
while it waits for a locked custom role. The pending count runs after
the locks, so a message that commits while the down waits still stops
it. Seed a flagged message without an automation and an automation
queued message without the flag.
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 Security Review
Security review completed. No security issues were found in this pull request.
Reviewed commit: fc3dde7420
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: fc3dde7420
ℹ️ 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.
…of allow lists
chat_queued_messages gets a CHECK that automation_id and input_id are
both NULL or both set, as the column comment says and as migration
000609 had. The down drops it.
A token's allow list cannot name chat_automation yet. During a rolling
upgrade, a replica of the previous release fails to load an API key
whose allow list names a resource type it does not know.
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.
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: 3f3d57ff4c
ℹ️ 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.
🛡️ Codex Security Review
Security review completed. No security issues were found in this pull request.
Reviewed commit: 3f3d57ff4c
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.
…l it is encrypted
The dbcrypt store does not encrypt chat_automations.webhook_secret yet.
With a cipher loaded, an insert that carries a webhook secret now fails
instead of storing the secret in plaintext. Inserts without a secret,
and inserts on a store without ciphers, pass through unchanged.
The PR that adds the encryption replaces this refusal.
… has no hint
No caller prints a PostgreSQL HINT, so the guidance must stay in the
message.
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. 🚀
Reviewed commit: bc579c4080
ℹ️ 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: bc579c4080
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 was referenced Nov 12, 2026
This branch was previously deployed
1 inactive
deployment
Reviewers
snagles
Awaiting requested review from snagles
jakehwll
Awaiting requested review from jakehwll
At least 1 approving review is required to merge this pull request.
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.
|