fix: classify chat lifecycle hook denials as policy decisions by ibetitsmike · Pull Request #27552 · coder/coder · GitHub
Skip to content

fix: classify chat lifecycle hook denials as policy decisions - #27552

Closed
ibetitsmike wants to merge 2 commits into
mike/chat-hooks/uat-fixesfrom
mike/chat-hooks/hook-denied-kind
Closed

fix: classify chat lifecycle hook denials as policy decisions#27552
ibetitsmike wants to merge 2 commits into
mike/chat-hooks/uat-fixesfrom
mike/chat-hooks/hook-denied-kind

Conversation

@ibetitsmike

Copy link
Copy Markdown
Collaborator

Stack Context

Top of the chat lifecycle hooks stack (#27401#27428#27429#27430#27551). Fixes the last two UAT findings, which are coupled because both need a way to tell a policy decision apart from a failure.

What?

Adds a hook_denied chat error kind and a structured 403 denial response, then uses it on both surfaces that render hook outcomes.

  • An existing chat now titles a denial Blocked by policy instead of Request failed.
  • The landing composer renders both hook outcomes cleanly instead of falling through to a developer error alert with a stack trace, response data, and a Go to workspaces action.

Why?

The dispatch-failure path already returns a structured ChatHookDispatchFailedResponse carrying kind: "hook_dispatch_failed", which the frontend uses to title it Lifecycle hook failed. The denial path wrote a bare codersdk.Response with no kind, so the frontend could not classify it, fell back to generic, and rendered Request failed. A policy decision is not a failure, and the old title carried no signal about what happened.

This needs a backend discriminator rather than a frontend-only fix. Matching on status === 403 alone would misclassify ordinary permission errors, which reach the same create-form branch and must keep their existing treatment. That case is pinned by the pre-existing ForbiddenErrorWithRole story, which still passes unchanged.

No migration: ChatErrorKind is persisted only inside the JSONB chats.last_error column, and the decoder accepts unknown kinds and defaults only blank ones.

Testing

  • Backend denial-response coverage extended to assert the new kind.
  • Two new create-form stories for the 403 and 502 outcomes, asserting the titles and messages and the absence of Stack Trace and Response data.
  • Red-green verified: removing the two create-form branches fails exactly the two new stories, with the other 29 still passing.
  • make gen confirmed idempotent, golangci-lint, pnpm lint:types, pnpm lint:compiler, pnpm run lint-docs.

Mux prepared this PR on Mike's behalf.

Map the structured 403 denial body to the hook_denied kind so an existing
chat titles it 'Blocked by policy' instead of 'Request failed', and give the
create form dedicated branches for both hook outcomes so a policy decision no
longer renders as a developer error with a stack trace.
@github-actions

Copy link
Copy Markdown
Contributor

@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: 37a65f2d11

ℹ️ 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 on lines +1372 to +1373
const kind = isChatHookDeniedResponse(error.response?.data)
? "hook_denied"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add interaction coverage for existing-chat hook denials

When a lifecycle hook rejects a send or edit in an existing chat, this new branch changes the rendered title to Blocked by policy, but the added stories cover only AgentCreateForm; the existing AgentChatPage story exercises hook_dispatch_failed and has no hook_denied scenario. Add an AgentChatPage.stories.tsx play story that submits a message, returns the structured 403 response, and asserts the policy title and message so this behavior is covered as required by FE1.

AGENTS.md reference: site/AGENTS.md:L9-L10

Useful? React with 👍 / 👎.

@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@ibetitsmike
ibetitsmike deleted the mike/chat-hooks/hook-denied-kind branch July 27, 2026 22:27
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 27, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant