Thanks to visit codestin.com
Credit goes to github.com

Skip to content

DNM: feat: wire chat search filter into chats API - #27093

Closed
johnstcn wants to merge 2 commits into
cian/codagt-723-add-search-parsing-to-searchquerychatsfrom
cian/codagt-725-wire-search-into-chats-api
Closed

DNM: feat: wire chat search filter into chats API#27093
johnstcn wants to merge 2 commits into
cian/codagt-723-add-search-parsing-to-searchquerychatsfrom
cian/codagt-725-wire-search-into-chats-api

Conversation

@johnstcn

@johnstcn johnstcn commented Jul 8, 2026

Copy link
Copy Markdown
Member

Wires the search: filter (parsed in #27090, executed in #27009) into GET /api/experimental/chats. Part of the stack #26968 <- #26995 <- #27009 <- #27090 <- this.

The handler now passes Search through to GetChats and preflights the search text with a new ChatSearchQueryIsEmpty query (numnode(websearch_to_tsquery(...)) = 0). Input that tokenizes to nothing (e.g. search:"!!!") returns a 400 validation error on search instead of silently matching nothing. The preflight only runs when a search term is present, so non-search requests make no extra DB calls. The swagger q parameter description now documents search:.

dbauthz note: ChatSearchQueryIsEmpty evaluates a pure function and touches no rows; there is no exact precedent for authz on side-effect-free queries, so it asserts ResourceChat read to match the calling context (listing chats).

Linear: https://linear.app/codercom/issue/CODAGT-725

Manual smoke (httptest server, seeded chat with title "deploy pipeline alpha" and message body "kubernetes cluster restart", search_tsv backfilled):

  • q=search:"kubernetes" -> 200, returns the chat (matched via message body)
  • q=search:"!!!" -> 400, Invalid chat search query. with validation on search
  • q=repo:coder search:"pipeline" -> 200, [] (search matches but repo filter excludes; both applied)
Implementation plan

CODAGT-725: Wire search: into chats API

Issue: https://linear.app/codercom/issue/CODAGT-725
Depends on: parser (#27090) and GetChats param (#27009).
Branch: cian/codagt-725-wire-search-into-chats-api, stacked on the 723 branch (stack: #26968 <- #26995 <- #27009 <- #27090 <- this).
Deliverable: one draft PR. Handler + one validation query; no frontend.

Requirements

  1. listChats in coderd/exp_chats.go: searchquery.Chats already returns GetChatsParams including Search (723); confirm the handler passes the parsed params through unmodified (it likely already does; the real work is validation). No readiness/503 logic.
  2. Empty-after-tokenization preflight, only when params.Search != "":
    • New sqlc query, e.g. in queries/chats.sql:

      -- name: ChatSearchQueryIsEmpty :one
      SELECT numnode(websearch_to_tsquery('simple', @search::text)) = 0;

      numnode = 0 is the canonical "tsquery is empty" check; punctuation-only input like !!! tokenizes to nothing.

    • If empty: 400 with a codersdk.ValidationError on field search, message like "Search query contains no searchable words." (match the file's existing validation error phrasing).

    • dbauthz for the new method: it evaluates a pure function, touches no rows. Find the nearest precedent for authz on side-effect-free queries and mirror it; if none fits, assert ResourceChat read (the caller is listing chats anyway). Flag the choice in the PR.

  3. Swagger annotation on the list-chats endpoint: document the search filter in the q parameter description. make gen to regenerate API docs; no new endpoint, no codersdk shape change.

Red: tests first

Handler tests in coderd/exp_chats_test.go (find the existing listChats tests and mirror fixtures; populate search_tsv by executing the 722 backfill query against the test DB store):

  1. Happy path: q=search:"<term>" returns chats matching by message body; a chat matching by title also returned; non-matching chat absent.
  2. q=search:"!!!" -> 400, validation error on search, message matches.
  3. Composition: q=repo:x search:"term" filters both; archived default behavior at the API layer unchanged (handler-level default pinned here, per the 724 note that NULL-vs-false lives in the handler).
  4. No search: -> existing behavior unchanged (existing tests remain green; add nothing unless a gap shows).
  5. Parser errors surface as 400 (e.g. search:a title:b) - confirm the existing searchquery-error path already covers this; add one case asserting the mutual-exclusion error reaches the client.

Green: implementation

  1. Add the sqlc query; make gen (querier/dbauthz/dbmetrics/dbmock).
  2. Wire the preflight + validation error into listChats.
  3. Update the swagger annotation; make gen again for docs.
  4. Tests green; make fmt && make lint.

Refactor

  • Confirm the preflight sits behind the params.Search != "" guard and adds zero DB calls to non-search requests.
  • Error message consistency with neighboring validation errors.

Verification


This PR was generated by Coder Agents on behalf of @johnstcn.

@linear-code

linear-code Bot commented Jul 8, 2026

Copy link
Copy Markdown

CODAGT-725

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Docs preview

📖 View docs preview for docs/reference/api/chats.md

@johnstcn johnstcn changed the title feat(coderd): wire chat search filter into chats API feat: wire chat search filter into chats API Jul 8, 2026
@johnstcn
johnstcn force-pushed the cian/codagt-723-add-search-parsing-to-searchquerychats branch from 3e6ec5b to 8504a14 Compare July 8, 2026 14:30
@johnstcn
johnstcn force-pushed the cian/codagt-725-wire-search-into-chats-api branch from 8017226 to ac63be7 Compare July 8, 2026 14:31
@johnstcn
johnstcn force-pushed the cian/codagt-723-add-search-parsing-to-searchquerychats branch from 8504a14 to fc7c727 Compare July 8, 2026 14:52
@johnstcn
johnstcn force-pushed the cian/codagt-725-wire-search-into-chats-api branch from ac63be7 to cfeeeba Compare July 8, 2026 14:53
@johnstcn
johnstcn force-pushed the cian/codagt-723-add-search-parsing-to-searchquerychats branch from fc7c727 to 5384a41 Compare July 8, 2026 18:39
@johnstcn
johnstcn force-pushed the cian/codagt-725-wire-search-into-chats-api branch from cfeeeba to 744fea9 Compare July 8, 2026 18:40
@johnstcn
johnstcn force-pushed the cian/codagt-723-add-search-parsing-to-searchquerychats branch from 5384a41 to 688db9b Compare July 8, 2026 19:17
@johnstcn
johnstcn force-pushed the cian/codagt-725-wire-search-into-chats-api branch from 744fea9 to d50be39 Compare July 8, 2026 19:17
@johnstcn
johnstcn force-pushed the cian/codagt-723-add-search-parsing-to-searchquerychats branch from 688db9b to e740826 Compare July 8, 2026 19:34
@johnstcn
johnstcn force-pushed the cian/codagt-725-wire-search-into-chats-api branch from d50be39 to e9896e7 Compare July 8, 2026 19:34
@johnstcn

johnstcn commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Chat: Review posted | View chat
Requested: 2026-07-08 23:02 UTC by @johnstcn
Spend: $38.72 / $100.00

Review history
  • R1 (2026-07-08): 15 reviewers, 3 Nit, 1 P3, COMMENT. Review
  • R2 (2026-07-08): 12 reviewers, 3 Nit, 1 P3, APPROVE. Review

deep-review v0.9.0 | Round 2 | 388bac7..6dc307b

Last posted: Round 2, 4 findings (1 P3, 3 Nit), APPROVE. Review

Finding inventory

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P3 Author fixed (f11c5cc) exp_chats_test.go:2033 backfillSearchTsv is dead weight (no messages inserted in subtest) R1 Bisky Yes
CRF-2 Nit Author fixed (f11c5cc) dbauthz.go:1841 Comment restates code in three lines for one line of why R1 Gon (downgraded from P2) Yes
CRF-3 Nit Author fixed (f11c5cc) chats.sql:335 Comment restates numnode check visible in SQL R1 Gon (downgraded from P2) Yes
CRF-4 Nit Author fixed (f11c5cc) exp_chats.go:359 Comment explains tsquery internals not needed at call site R1 Gon (downgraded from P2) Yes
CRF-5 Nit Dropped by orchestrator (taste, explicit params idiomatic Go) exp_chats_test.go:1952 insertMessage takes params it could close over R1 Bisky No
CRF-6 Note Dropped by orchestrator (pre-existing pattern, not this PR) exp_chats.go:335 Swagger q description is a run-on sentence R1 Leorio No
CRF-7 Note Dropped by orchestrator (follows existing convention) exp_chats.go:365 500 error message "Failed to validate" follows convention R1 Leorio No
CRF-8 P2 Dropped by orchestrator (author adopted exact CRF-3 suggested text; further trim is churn) chats.sql:336 Second line of SQL comment restates caller intent R2 Gon No

Round log

Round 1

Panel: Bisky, Hisoka, Mafu-san, Mafuuu, Pariston, Gon, Leorio, Ging-Go, Chopper, Knuckle, Komugi, Ryosuke, Meruem, Kite, Knov (wildcard). Netero: no findings. 1 P3, 3 Nit, 3 dropped (2 Note, 1 Nit). Reviewed against e740826..e9896e7.

Round 2

Churn guard: PROCEED (4/4 addressed). Panel: Bisky, Hisoka, Mafu-san, Mafuuu, Pariston, Gon, Leorio, Komugi, Ging-Go, Chopper, Kite, Meruem (wildcard). Netero: no findings, all R1 fixes verified. 1 new finding (CRF-8 P2) dropped as churn. All R1 findings verified fixed. No findings to post. Reviewed against 388bac7..6dc307b.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Komugi flake/determinism
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Clean, well-scoped PR. The preflight validation using the same websearch_to_tsquery('simple', ...) as the actual search eliminates divergence between what the validator accepts and what the query matches. The dbauthz design decision is flagged honestly, the test coverage is proportional (5 cases, 3.8:1 test-to-production ratio), and every handler path is exercised against a real DB.

15 reviewers found 1 P3 and 3 Nits. Severity breakdown: 0 P0, 0 P1, 0 P2, 1 P3, 3 Nit.

"I tried to build a case against this change and couldn't. The problem is correctly understood, the solution is proportional, and the fix is at the right level." *~ Pariston*

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/exp_chats_test.go
}

bothMatch := createChat(t, db, firstUser, modelConfig.ID, "kubernetes in coder repo")
linkRepo(bothMatch.ID, "[email protected]:acme/widget.git")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3 [CRF-1] backfillSearchTsv processes zero rows here because no insertMessage calls precede it. The subtest creates four chats and links repos, but never inserts chat messages. The search matches by title (inline in the query), not by message-body search_tsv. The call is a no-op that misleads readers into thinking message-body search is being tested in this composition scenario.

Either drop the call, or insert a message on one of the chats to also exercise message-body search composed with the repo filter. The latter would strengthen coverage.

(Bisky)

🤖

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

resolved: [CRF-1] Added a body-only-match chat (bodyMatch) plus a wrong-repo counterpart, so the composition subtest now exercises search_tsv and asserts message-body matches appear only when the repo filter allows. Fixed in f11c5cc.

Reply generated by Coder Agents on behalf of @johnstcn.

Comment thread coderd/database/dbauthz/dbauthz.go Outdated
Comment on lines +1841 to +1843
// This evaluates a pure tokenization function and touches no rows,
// but the only caller is validating a chat list request, so gate it
// on the same read permission as listing chats.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit [CRF-2] Three lines of mechanism narration for one line of why-not-what. The code already shows authorizeContext(ctx, policy.ActionRead, rbac.ResourceChat) and q.db.ChatSearchQueryIsEmpty. The useful content is that the permission mirrors the calling context.

Suggested:

// Pure function, no rows. Gates on chat read to match the listing caller.

(Gon)

🤖

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

resolved: [CRF-2] Adopted verbatim in f11c5cc.

Reply generated by Coder Agents on behalf of @johnstcn.

Comment thread coderd/database/queries/chats.sql Outdated
Comment on lines +335 to +338
-- Reports whether the search text tokenizes to an empty tsquery, e.g.
-- punctuation-only input like '!!!'. numnode = 0 is the canonical
-- "tsquery is empty" check. Used to return a validation error instead
-- of silently matching nothing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit [CRF-3] The comment restates numnode(...) = 0 as "numnode = 0 is the canonical 'tsquery is empty' check" when the SQL already says exactly that. This comment propagates to querier.go and queries.sql.go, so trimming it fixes all three copies.

Suggested:

-- Reports whether search text tokenizes to an empty tsquery (e.g. '!!!').
-- Used to reject input that would silently match nothing.

(Gon)

🤖

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

resolved: [CRF-3] Adopted verbatim; regenerated querier.go and queries.sql.go copies included. Fixed in f11c5cc.

Reply generated by Coder Agents on behalf of @johnstcn.

Comment thread coderd/exp_chats.go Outdated
Comment on lines +359 to +360
// Reject search input that tokenizes to an empty tsquery (e.g.
// punctuation-only text like "!!!"); it would silently match nothing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit [CRF-4] The function name ChatSearchQueryIsEmpty and the error message "Search query contains no searchable words" already communicate the intent. The comment's useful content is the consequence ("it would silently match nothing"). "Tokenizes to an empty tsquery" and the parenthetical example are mechanism the reader doesn't need at the call site.

Suggested:

// Reject text that tokenizes to nothing; it would silently match no rows.

(Gon)

🤖

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

resolved: [CRF-4] Adopted verbatim in f11c5cc.

Reply generated by Coder Agents on behalf of @johnstcn.

@johnstcn
johnstcn force-pushed the cian/codagt-723-add-search-parsing-to-searchquerychats branch from 7262cc4 to 388bac7 Compare July 8, 2026 22:43
@johnstcn
johnstcn force-pushed the cian/codagt-725-wire-search-into-chats-api branch from f11c5cc to 6dc307b Compare July 8, 2026 22:44

johnstcn commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

/coder-agents-review

All prior findings addressed and replied to inline. Requesting a convergence pass.

🤖 Requested via Coder Agents on behalf of @johnstcn.

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All four R1 findings verified fixed. CRF-1's fix went beyond removing the dead call: it added genuine coverage proving message-body search composition with repo filters works end to end. CRF-2/3/4 comment trims applied uniformly across all three locations.

12 reviewers, 0 new findings. Netero confirmed all fixes independently. The preflight uses the same websearch_to_tsquery('simple', ...) as the actual search, the dbauthz gate is documented and tested, and the test suite covers happy path, validation, composition, regression, and mutual exclusion.

"The code held up. ♠" *~ Hisoka*

🤖 This review was automatically generated with Coder Agents.

@johnstcn
johnstcn force-pushed the cian/codagt-723-add-search-parsing-to-searchquerychats branch from 388bac7 to ed6453d Compare July 9, 2026 07:20
@johnstcn
johnstcn force-pushed the cian/codagt-725-wire-search-into-chats-api branch from 6dc307b to 0228edb Compare July 9, 2026 07:22
@johnstcn johnstcn changed the title feat: wire chat search filter into chats API DNM: feat: wire chat search filter into chats API Jul 9, 2026
@github-actions github-actions Bot added the stale This issue is like stale bread. label Jul 25, 2026
@github-actions github-actions Bot closed this Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale This issue is like stale bread.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant