Repository navigation
fix(server): worktree thread lookups match any spelling of the path #36802
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a64eefd
686f678
6918807
a545ab4
e25cfcd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -95,6 +95,7 @@ import * as SqlClient from "effect/sql/SqlClient"; | |
| import type * as Statement from "effect/sql/Statement"; | ||
|
|
||
| import { MCP_APP_OUTPUT_KEY } from "@t3tools/shared/mcpApp"; | ||
| import { normalizeProjectPathForComparison } from "@t3tools/shared/path"; | ||
| import { threadHtmlRenderAttachmentIds } from "../attachmentStore.ts"; | ||
| import { readableSearchText } from "./messageSearchIndex.ts"; | ||
| import { | ||
|
|
@@ -1095,6 +1096,7 @@ type ShellThreadRow = { | |
| readonly thread_id: string; | ||
| readonly payload_json: string; | ||
| readonly forked_from_run_source_thread_id: string | null; | ||
| readonly worktree_path: string | null; | ||
| readonly latest_run_id: string | null; | ||
| readonly latest_run_status: string | null; | ||
| readonly latest_run_requested_at: string | null; | ||
|
|
@@ -7687,6 +7689,7 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> = | |
| THEN json_extract(t.payload_json, '$.forkedFrom.threadId') | ||
| ELSE NULL | ||
| END AS forked_from_run_source_thread_id, | ||
| json_extract(t.payload_json, '$.worktreePath') AS worktree_path, | ||
| presented.run_id AS latest_run_id, | ||
| presented.status AS latest_run_status, | ||
| presented.requested_at AS latest_run_requested_at, | ||
|
|
@@ -7883,8 +7886,20 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> = | |
| }${ | ||
| worktreePath === undefined | ||
| ? sql`` | ||
| : sql` AND CASE WHEN json_valid(t.payload_json) | ||
| THEN json_extract(t.payload_json, '$.worktreePath') END = ${worktreePath}` | ||
| : // The key matches orchestration_v2_projection_threads_worktree_path_key_idx, so an | ||
| // ASCII path does not scan the table. SQLite lower() folds only ASCII, so a query | ||
| // that itself contains another character also admits those stored paths; | ||
| // readShellSnapshotRows keeps the exact comparison. | ||
| sql` AND (CASE WHEN json_valid(t.payload_json) | ||
| THEN lower(rtrim(replace(trim(json_extract(t.payload_json, '$.worktreePath')), '\\', '/'), '/')) | ||
| END = lower(rtrim(replace(trim(${worktreePath}), '\\', '/'), '/'))${ | ||
| /^[\u0000-\u007e]*$/u.test(worktreePath) | ||
| ? sql`` | ||
| : sql` OR ( | ||
|
Comment on lines
+7896
to
+7898
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Preserve Unicode-to-ASCII matches for ASCII queries. With a stored path Make the fallback cover stored characters that lowercase to ASCII, even when the query is ASCII. Extend the existing Windows assertion with this query direction.
|
||
| json_valid(t.payload_json) | ||
| AND json_extract(t.payload_json, '$.worktreePath') GLOB '*[^ -~]*' | ||
| )` | ||
| })` | ||
| }${threadIds === undefined ? sql`` : sql` AND t.thread_id IN (SELECT value FROM json_each(${encodeIdList(threadIds)}))`} | ||
| ORDER BY t.updated_at ASC, t.thread_id ASC | ||
| `; | ||
|
|
@@ -8498,7 +8513,16 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> = | |
| const readShellSnapshotRows = Effect.fn("ProjectionStore.readShellSnapshotRows")(function* ( | ||
| options: ShellSnapshotOptions | undefined, | ||
| ) { | ||
| const targetThreadRows = yield* selectShellThreadRows(undefined, options); | ||
| const worktreeKey = | ||
| options?.worktreePath === undefined | ||
| ? undefined | ||
| : normalizeProjectPathForComparison(options.worktreePath); | ||
| const targetThreadRows = (yield* selectShellThreadRows(undefined, options)).filter( | ||
| (row) => | ||
| worktreeKey === undefined || | ||
| (row.worktree_path !== null && | ||
| normalizeProjectPathForComparison(row.worktree_path) === worktreeKey), | ||
| ); | ||
| const targetThreadIds = new Set(targetThreadRows.map((row) => ThreadId.make(row.thread_id))); | ||
| const rowsByThreadId = new Map( | ||
| targetThreadRows.map((row) => [ThreadId.make(row.thread_id), row] as const), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import * as Effect from "effect/Effect"; | ||
| import * as SqlClient from "effect/sql/SqlClient"; | ||
|
|
||
| export default Effect.gen(function* () { | ||
| const sql = yield* SqlClient.SqlClient; | ||
|
|
||
| // Keep this expression identical to ProjectionStore's loose worktree filter. | ||
| yield* sql` | ||
| CREATE INDEX IF NOT EXISTS orchestration_v2_projection_threads_worktree_path_key_idx | ||
| ON orchestration_v2_projection_threads( | ||
| CASE WHEN json_valid(payload_json) | ||
| THEN lower(rtrim(replace(trim(json_extract(payload_json, '$.worktreePath')), '\\', '/'), '/')) | ||
| END | ||
| ) | ||
| `; | ||
| }); |
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.
Match JavaScript whitespace trimming in the SQL key.
With a stored path
/repo/wtand query"\t/repo/wt\n",normalizeProjectPathForComparisonconsiders both paths equal. SQLite's defaulttrim()removes spaces, not tabs or newlines. The ASCII query also disables the fallback, so SQL discards the thread before the exact comparison. (www3.sqlite.org)Use the JavaScript whitespace set for both SQL keys and the matching index expression. Extend the existing service test with this case.