Repository navigation
fix(jira): search plain text when task search input isn't JQL - #22900
AmethystLiang merged 9 commits into
Conversation
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: 📥 CommitsReviewing files that changed from the base of the PR and between 02e12bb931a6f960933dcc9769555e08ea77098c and 6b7b719bf8c338f2f86a8050979df0f02206a5dd. 📒 Files selected for processing (9)
🚧 Files skipped from review as they are similar to previous changes (5)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review. 📝 WalkthroughWalkthroughJira search accepts text queries and possible JQL. When Jira rejects a JQL query with a 400 error and a text query can be built, search retries with text-search JQL and records the rejection reason. The Jira issue list can display a fallback notice with optional error details. Search guidance and translations were updated. Shared text-query construction is also used for workspace source searches. Priority: ➖ Normal Severity of issue fixed: Medium Merge Risk: ⚪ Minimal · up to Plain-text searches and JQL fallback have no identified blocker in the reviewed changes; the PR appears ready to merge after normal checks. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The fallback changes which searches can return results, but the inspected path continues to use the existing Jira connection and read controls. No new security issue was verified. Jira-side authorization and some surrounding changes were not fully covered, so the assessment is not minimal risk. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Full details: Out of Scope Changes checkExplanation The Jira search changes, shared builder, localized text, and related tests support issue Full details: Docstring CoverageExplanation Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 15 functions across 16 files. (6 skipped: 6 unsupported.)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Advanced
Run ID: 84d758c9-fead-48b4-ab1b-32e08d697d0b
📥 Commits
Reviewing files that changed from the base of the PR and between 646e9a5 and ad59f4b5462937d2d77ee0b0996bd80aa442d137.
📒 Files selected for processing (20)
src/renderer/src/components/new-workspace/smart-workspace-source-results.test.tssrc/renderer/src/components/task-page-jira-load-state.test.tssrc/renderer/src/components/task-page-jira-load-state.tssrc/renderer/src/components/task-page-jira-search.test.tssrc/renderer/src/components/task-page-jira-search.tssrc/renderer/src/components/task-page-jira-text-fallback-notice.test.tsxsrc/renderer/src/components/task-page-jira-text-fallback-notice.tsxsrc/renderer/src/components/task-page/jira/Content.tsxsrc/renderer/src/components/task-page/jira/Filters.tsxsrc/renderer/src/components/use-task-page-jira-list-effects.tssrc/renderer/src/components/use-task-page-jira-list-state.tssrc/renderer/src/i18n/locales/en.jsonsrc/renderer/src/i18n/locales/es.jsonsrc/renderer/src/i18n/locales/fr.jsonsrc/renderer/src/i18n/locales/ja.jsonsrc/renderer/src/i18n/locales/ko.jsonsrc/renderer/src/i18n/locales/zh.jsonsrc/shared/jira-search-input-jql.test.tssrc/shared/jira-search-input-jql.tssrc/shared/new-workspace/smart-workspace-source-results.ts
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to GitHub limitations.
🟡 Minor · Open a unique exact issue-key match after search. · task-page-jira-search.ts:15-22
src/renderer/src/components/task-page-jira-search.ts:15-22
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winOpen a unique exact issue-key match after search.
When the user submits an exact Jira key, the search effect stores the matching issue in the list but does not call
openJiraDetailPage. The user must click the row again. Open the result when the exact-key search returns one matching issue.Suggested fix
+import { JIRA_ISSUE_KEY_PATTERN } from '../../shared/jira-issue-url' ... jiraTaskSourceContext, + openJiraDetailPage, ... setJiraIssues(issues) setJiraJqlRejection(jqlRejection) setJiraLoading(false) + if (JIRA_ISSUE_KEY_PATTERN.test(trimmed)) { + const matches = issues.filter( + (issue) => issue.key.toUpperCase() === trimmed.toUpperCase() + ) + if (matches.length === 1) { + openJiraDetailPage(matches[0]) + } + } const projectScope = getSingleJiraProjectScope(issues)
🟡 Minor · Mount the status region before adding the fallback… · task-page-jira-text-fallback-notice.tsx:11-22
src/renderer/src/components/task-page-jira-text-fallback-notice.tsx:11-22
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winMount the status region before adding the fallback message.
Contentcurrently mounts a populatedrole="status"element only after the fallback result arrives. Astatuslive region does not reliably announce its initial content. Repeated searches remove and recreate the region instead of updating an existing one, so screen-reader users can miss the fallback notice.Suggested fix
- {!jiraStatus.credentialError && !jiraError && jiraJqlRejection !== null ? ( - <TaskPageJiraTextFallbackNotice key={jiraJqlRejection} reason={jiraJqlRejection} /> - ) : null} + <TaskPageJiraTextFallbackNotice + reason={jiraJqlRejection ?? ''} + visible={!jiraStatus.credentialError && !jiraError && jiraJqlRejection !== null} + />-export function TaskPageJiraTextFallbackNotice({ reason }: { reason: string }): React.JSX.Element { +export function TaskPageJiraTextFallbackNotice({ + reason, + visible = true +}: { + reason: string + visible?: boolean +}): React.JSX.Element { ... <div role="status" - className="flex items-start gap-2 border-b border-border/50 bg-muted/35 px-4 py-2 text-xs text-muted-foreground" + aria-live="polite" + className={ + visible + ? 'flex items-start gap-2 border-b border-border/50 bg-muted/35 px-4 py-2 text-xs text-muted-foreground' + : 'sr-only' + } > - <Info className="mt-0.5 size-3.5 flex-none" /> - <div className="min-w-0 flex-1"> + {visible ? ( + <> + <Info className="mt-0.5 size-3.5 flex-none" /> + <div className="min-w-0 flex-1"> ... - </div> + </div> + </> + ) : null} </div>
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Advanced
Run ID: 2090b080-5c79-41d0-9319-9d59cc1a6de4
📥 Commits
Reviewing files that changed from the base of the PR and between ad59f4b5462937d2d77ee0b0996bd80aa442d137 and fd7280edeeb9151a8e2b943160ffc3c1ab9bf18f.
📒 Files selected for processing (2)
src/renderer/src/components/task-page-jira-text-fallback-notice.test.tsxsrc/renderer/src/components/task-page-jira-text-fallback-notice.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- src/renderer/src/components/task-page-jira-text-fallback-notice.tsx
- src/renderer/src/components/task-page-jira-text-fallback-notice.test.tsx
Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.
There was a problem hiding this comment.
Important
Plain-text searches that contain a token-splitting punctuation character not in the neutralized set (. , ; # % $) can silently return zero results — Jira's wildcard text terms are not analyzed, so the trailing * never matches.
Reviewed changes
- JQL-vs-text classifier — new
mayBeJqldecides "could this be JQL?" from a closed operator set;buildJiraTextSearchJqllowercases, strips Lucene syntax, and emitstext ~ "…*"orkey = "…". - Task-page search orchestration —
searchTaskPageJiraIssuesskips JQL for operator-free input, retries a rejected JQL query as text, and returns Jira's reason for the notice; auth/rate-limit/network failures surface unchanged. - Fallback notice — new
TaskPageJiraTextFallbackNoticerenders "Showing text matches" with Jira's rejection reason collapsed behind a Details control. - State/effects plumbing —
jiraJqlRejectionthreaded throughuse-task-page-jira-list-state.tsanduse-task-page-jira-list-effects.tsintojira/Content.tsx; the list-state hook swaps itsasassertion forObject.assign. - New-workspace reuse —
buildJiraIssueSearchJqlnow delegates to the shared text builder, so that path also lowercases and strips punctuation. - Copy/i18n — placeholder and empty-state strings updated in all six locales.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in the latest commit. The un-neutralized-punctuation finding from the prior review still stands open.
Reviewed changes (delta since ad59f4b)
- Accessibility —
TaskPageJiraTextFallbackNoticenow carriesrole="status", turning the notice into a polite live region so screen readers announce it; the notice test asserts the live region contains "Showing text matches".
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Wildcard dropped for punctuation-final text searches —
buildJiraTextSearchJqlnow appends the trailing*only when the last word matches/^[\p{L}\p{N}']+$/u, so inputs likefix login.,C#,100%, and$5are analyzed as text instead of matched as unanalyzed wildcard terms. This addresses the prior finding on un-neutralized punctuation; mid-string punctuation (login. fix*) keeps the wildcard, which is correct because only the final whitespace-separated clause is wildcardified. - Fallback notice live region stays mounted —
TaskPageJiraTextFallbackNoticenow always renders itsrole="status"wrapper and renders the body in akey-ed child, andContent.tsxalways mounts the notice, so screen readers announce content changes inside an existing region rather than a freshly inserted one. Adds a rerender test asserting the same region node survives.
Verified the changed tests pass under config/vitest.config.ts (jira-search-input-jql 63, task-page-jira-search 18, task-page-jira-text-fallback-notice 3). The earlier pullfrog thread on the punctuation gap is addressed and resolved.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes (delta since prior pullfrog review at e11e9be)
- Hyphenated operator words now stay plain text —
JQL_OPERATOR_PATTERNswaps\b(?:in|is|was|changed)\bfor(?<![\w-])(?:in|is|was|changed)(?![\w-]), sosign-in pageandbuilt-in was-no longer look like JQL and skip the wasted JQL round-trip plus the fallback notice;jira-search-input-jql.test.tsadds both cases. - Fallback notice guard simplified —
Content.tsxdrops the redundant!jiraErrorfrom the notice'sreasonprop while keeping the credential-error guard. Neutral: the effect clearsjiraJqlRejectionat the start of every run and setsjiraErroronly in.catch, so both can never be non-null.
Verified the regex against status WAS Done, project in (Open), assignee CHANGED, ORDER BY updated DESC (still JQL) and sign-in page, built-in was-, was-, x-in (now text); no legitimate JQL regresses since valid operators are never hyphen-adjacent. Ran jira-search-input-jql (65), task-page-jira-search (18), task-page-jira-text-fallback-notice (3), and task-page-jira-load-state (13) — all pass. Prior pullfrog thread 4106907181 is addressed and resolved.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes (delta since prior pullfrog review at 02e12bb)
- Merged
origin/maininto the PR branch — the only new commit; it integrates main's newforceoption on the Jira list/search path without dropping the PR's text-search wrapper. forcethreaded through the text-search wrapper —use-task-page-jira-list-effects.tsnow passesforceintosearchJiraIssuesinside thesearchTaskPageJiraIssuesclosure, so a manual refresh bypasses the cache on both the JQL attempt and any text fallback. Verified the option reaches all three call sites and the PR's own changes survive the merge intact.- Regression test added —
passes force through the text-search wrapper on refreshasserts the nonce change deliverssearchJiraIssues('text ~ "fix login*"', 50, { sourceContext: null, force: true }).
Ran the affected suites on the merged head: jira-search-input-jql (65), task-page-jira-search (18), task-page-jira-text-fallback-notice (3), use-task-page-jira-list-effects (2) — 88 pass. The mobile/src/terminal/terminal-live-text-commit.test.ts hunk in the full diff is prettier-only base-staleness noise, not part of this PR.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
Review: right approach, three things to tighten before mergeThe design is right. Jira acts as the JQL parser, one up-front check skips input that can't possibly be JQL, and a text search runs when Jira returns 400. A local JQL parser would always fall behind custom fields and plugins. A Text/JQL toggle would add UI for something this handles on its own. The rest looks correct too: the retry only runs on 400, a non-400 failure on the retry shows as itself, and the text-search builder is shared with new-workspace. Tests pass locally (6 files, 145 tests). Everything below was checked against Jira's public Data Center API at jira.atlassian.com, using anonymous read-only requests. 1.
|
| JQL | Jira response |
|---|---|
project in JRASERVER |
400: "Operator 'in' does not support the non-list value…" |
status is Open |
400: "Operator 'is' does not support searching for non-empty values…" |
So the version below is exactly as safe as the current rule ("input that can't parse as JQL skips it"):
const JQL_OPERATOR_PATTERN =
/[=~<>]|(?<![\w-])(?:was|changed)(?![\w-])|(?<![\w-])in\s*(?:\(|[a-z_][\w.]*\s*\()|(?<![\w-])is\s+(?:not\s+)?(?:empty|null)(?![\w-])|^order\s+by\b/iI ran it against every JQL case in jira-search-input-jql.test.ts, plus sprint in openSprints(), sprint in openSprints (), assignee in membersOf("devs"), status IN(Done) and labels is not empty. All of them are still classified as JQL. sign in, crash in terminal, login is slow and this is broken now go straight to a text search in one request, so the this is broken fixture needs to move to the other list. was and changed still need Jira to decide, because field WAS value is too loose to rule out locally. That's fine.
2. JQL typos come back as convincing-looking results (please consider)
When clearly written JQL has a bad value, the text fallback returns unrelated issues:
| Typed | Fallback query | Issues returned |
|---|---|---|
project = NOPE |
text ~ "project = nope*" |
49 |
assignee = jdoe |
text ~ "assignee = jdoe*" |
10 |
Before this PR, these showed a clear error. Now they show a list that looks like an answer, a quiet one-line note, and Jira's reason ("The value 'NOPE' does not exist for the field 'project'") collapsed under Details.
I understand why the fallback covers every 400: ~, < and > do show up in plain searches (~/.zshrc, <br>), so I wouldn't bring the error back for all operator input. But = and != almost never appear in plain searches. Suggestion: when the input contains = or !=, open Details by default (or show the note as a warning). A JQL typo is then visible without a click, and plain searches keep the current collapsed note.
3. Words shaped like issue keys never get a text search (should fix, or file a follow-up)
Anything matching JIRA_ISSUE_KEY_PATTERN becomes key = "…", and that path has no fallback:
gpt-4,utf-8andcovid-19becomekey = "GPT-4"(and so on), which returns 200 with 0 results. No text search runs.- If the project exists but the issue doesn't (for example
JRASERVER-99999999, or a mistyped key on your own site), Jira returns 400: "An issue with key '…' does not exist for field 'key'".searchTaskPageJiraIssuesrethrows it, and with a single site selected the banner says "Couldn't load Jira issues. Try again in a moment." Retrying won't help.
Suggestion: when the key query returns 400 or no results, run the text search instead. The new-workspace search already behaved this way before this PR, but the description now promises that plain words search issue text on the Tasks page.
Related: the description says "An issue key (abc-12) opens that issue directly". The code narrows the list to that issue but doesn't open it. Either update the description or add the open (CodeRabbit suggested this too).
Nit: two parsers for the same error prefix
The comment on BAD_REQUEST_PATTERN explains that it can't be anchored because local IPC errors start with "Error invoking remote method…". But getErrorCode a few lines above is anchored (/^Error\s+(\d{3})/). For those same local errors it never finds the status code and falls back to guessing from keywords. Making getErrorCode unanchored and building the 400 check on top of it would fix both, instead of keeping a second parser.
How these findings were found (review method)This review was run with Claude Code (an AI agent), and the findings were re-checked before posting. Here are the steps, so anyone can reproduce or reuse them:
Takeaway: every substantive finding came from step 3. Checking the PR's assumptions about Jira against Jira itself found them; reading the code didn't. |
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes (delta since prior pullfrog review at 6b7b719)
- Split the Jira JQL builders —
buildJiraTextSearchJqlis now composed from the newbuildJiraIssueKeyJql(key shape →key = "…", elsenull) andbuildJiraTextMatchJql(key shape ignored), so a caller can tell "key-shaped" apart from "text". The composed output is identical to the previous single-builder behaviour, so both the task page andnew-workspacebuildJiraIssueSearchJqlare unaffected. - Key-shaped input retried as text —
searchTaskPageJiraIssuesnow looks upkey = "ABC-12"first and, when no issue matches and the input still yields searchable words, retries the same input astext ~ "…*"instead of showing an empty list. A key that does match still costs a single request. - Tests — new unit tests assert the retry (
utf-8→key = "UTF-8"thentext ~ "utf 8*"), the no-retry-when-found case (sha-256), and the two split builders; each fails if the fix is reverted.
Verified all affected suites pass on fb23e02: jira-search-input-jql (77), task-page-jira-search (20), task-page-jira-text-fallback-notice (3), use-task-page-jira-list-effects (2), task-page-jira-load-state (13), smart-workspace-source-results (44). All prior pullfrog threads are resolved.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
The Tasks page sent every keystroke to Jira as JQL, so partial input like `s` failed with a 400. Fixes stablyai#22265
For plain-text searches the reason ("Field 'login' does not exist") reads as noise.
Jira doesn't split a wildcard term into words, so `login,*` or `c#*` matched nothing.
Input like `utf-8`, `sha-256` or `covid-19` matches the issue-key shape, so the search ran only `key = "UTF-8"` and showed an empty list with nothing to explain why. An empty exact-key lookup is a wrong guess, not an answer, so retry it as a text search — the same let-Jira-decide-then-fall-back rule the JQL path already uses. Splits the key and text builders apart so the caller can tell which branch it took; `buildJiraTextSearchJql` keeps its combined behaviour for the smart-workspace caller.
Bare `in`/`is` sent everyday phrases down the JQL path, a key lookup Jira answered with 400 never reached the text search, and a missing key like PROJ-1401 was read as a lost connection. One key-or-text search now serves the Tasks page and new-workspace, and one parser reads the status prefix.
fb23e02 to
1b3d989
Compare
|
@AmethystLiang thanks for the review. I re-ran each claim against jira.atlassian.com and they all hold. Everything is in 1b3d989 (the branch is also rebased onto current main). 1. 2. JQL typos. When the rejected input contains 3. Key-shaped words. Neil's commit covered the empty result. The 400 case ("An issue with key … does not exist") now falls back to text as well. Both cases live in one function, Nit. One parser now ( One more thing turned up while checking the key path: the store decided "sign-in failure" by looking for Left as is: |
There was a problem hiding this comment.
ℹ️ No critical issues — one regression in the new JQL-vs-text classifier, inline.
Reviewed changes (delta since the prior pullfrog review at fb23e02)
- Key-or-text search extracted and shared — new
jira-key-or-text-search.tsowns the "trykey = "…", fall back totext ~ "…"when the key lookup returns nothing or a 400" logic; both the task page and new-workspace now call it. IN/ISclassified by grammar —JQL_OPERATOR_PATTERNnow acceptsinonly before a list/function call andisonly beforeEMPTY/NULL, sosign in,crash in terminalandlogin is slowskip the JQL request entirely.- JQL rejection carries
likelyTypo— the reason string becameTaskPageJiraJqlRejection, and the fallback notice opens Details by default when the rejected input contains=. - Status parsing centralized — new
parseJiraStatusErrorreadsError <status>: <details>for the fallback detection, the error banner andlooksLikeJiraAuthError, so a 400 that merely mentions401(e.g.key 'PROJ-1401') no longer marks Jira disconnected. - New-workspace query handoff —
getJiraIssueSearchQueryreplacesbuildJiraIssueSearchJqland returns the raw trimmed query, so the key-then-text fallback applies on that path too.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
`input (raw)` and `init()` read as `in` + a function call and took the JQL path.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes (delta since the prior pullfrog review at 1b3d989)
INword boundary tightened in the JQL classifier —JQL_OPERATOR_PATTERNnow requires whitespace before an unquoted function name ((?<![\w-])in(?:\s+[a-z_][\w.]*|\s*"[^"]+"|\s*'[^']+')?\s*\(), soinput (raw),install (macos)andinit()no longer glue ontoinand are correctly classified as text; quoted function names may still follow directly since the quote ends the word. Adds those three as text cases plusstatus in\n(Open)as a JQL case. The prior pullfrog thread is addressed and resolved.
Verified the changed suite passes on this head: jira-search-input-jql (90 tests).
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
…lders Main's stablyai#22900 replaced buildJiraIssueSearchJql with a gate (getJiraIssueSearchQuery, which returns the trimmed query) and two builders in jira-search-input-jql. The merge carried no conflict here, so only the typecheck caught it — and swapping names alone would have sent plain text where the host expects JQL. The composer now gates on the query, then builds key JQL first and falls back to text match, which is what the old single function did and what desktop does. Compared old against new across the paths: issue keys, plain text and empty/whitespace are identical. Three inputs differ, and all three are main's fixes rather than regressions — quotes and parens are Lucene syntax the old builder pasted into JQL, and an uppercase AND leaked through as an operator.

ELI5
The Jira search box on the Tasks page only understood Jira's query language (JQL). Typing a normal word like
sortestsent it to Jira as a query, Jira rejected it, and you got an error instead of results. Now the box works for both: write JQL and it runs as JQL; type plain words and it searches issue text.What Changed
Before: typing
testin the Jira search showed "Jira couldn't run this JQL query" (HTTP 400: Expecting operator before the end of the query) and an empty list. This happens on every Jira server, not just on-prem.After:
s,fix login bug) search issue text. An issue key (abc-12) narrows the list to exactly that issue, ready to click.utf-8,sha-256,covid-19, or a mistyped key likeABC-99999) are tried as an issue key first. When Jira has no issue with that key, whether it answers with an empty list or with an error, they fall back to a text search.sign in,crash in terminal,login is slow) search issue text in one request, with no note.WAS,CHANGED, custom fields and plugin functions.login was sloworproject = NOPE) falls back to a text search, with a small note above the results saying it's showing text matches. Jira's reason is under Details. When the input contains=, it was almost certainly meant as JQL, so Details starts open and the typo ("The value 'NOPE' does not exist…") is visible without a click. Other searches keep the note collapsed.How it works:
ABC-12) becomeskey = "ABC-12". If Jira returns no issue or answers 400 ("An issue with key … does not exist"), the same input is retried as a text search. The shape alone isn't proof the user meant a key. One function does this for both the Tasks page and the new-workspace screen.= != < > ~ IN IS WAS CHANGED, or a leadingORDER BY), and Jira's grammar limits two of them:INmust be followed by a list or a function call, andISbyEMPTYorNULL. Input that doesn't fit can't be JQL, so it goes straight to a text search in one request.text ~ "<words>*". Characters that have special meaning in Jira's text search (( ) " [ ] { } ^ - && ||and others) are replaced with spaces, because Jira strips them from issue text when it indexes it anyway. Escaping them looks right but finds nothing. The text is also lowercased, soAND/OR/NOTare treated as ordinary words. Without this, a partially typedfix (loginorsay "hiwould recreate the same 400.The text-search builder is shared with the Jira source in the new-workspace screen, which already built
text ~queries. That path also had the punctuation problem, and it's fixed by the same change.Failed Jira requests reach the app as
Error <status>: <reason>. One parser now reads that prefix for the fallback, the error banner and the store's "was this a sign-in failure?" check. Before, the store looked for "401" anywhere in the message, so searching a missing key likePROJ-1401marked Jira as disconnected; it now trusts the status code when the message carries one.The only renderer-side state added is the fallback note. The request to Jira is unchanged (still a
jqlstring), and saved searches restore as before.also
use-task-page-jira-list-state.tsswitches from a type assertion toObject.assign(same runtime behaviour) because adding the new field inside the existing assertion fails the changed-code lint gate.Why
The box has been a JQL box since Jira support landed, and people rely on typing JQL there. So the fix keeps JQL as-is and adds plain-text search around it without changing what existing queries do. Deciding "is this JQL?" with patterns can't be done reliably (
status WAS Doneis valid JQL, whilevalue is nullmay or may not be, depending on the site's fields). So only one decision is made up front, and it's provably safe: input with no JQL operator at all can't be JQL. Everything else is decided by Jira's own response.Fixes #22265
Visual Proof
Before:

After:

Before:

After:

Testing
Reproduced the bug against Atlassian's public Jira Data Center (jira.atlassian.com):
jql=testandjql=sreturn the exact 400 from the issue.Ran the new search function against that live API.
s,test,fix login bug,jraserver-1,fix (login,say "hi,foo [bar,login -,OR xandC++ buildeach return results in one request.status WAS Done,assignee CHANGEDandvalue is nullrun as JQL.project = NOPEfalls back to text and shows Jira's reason. A lone(sends no request.Checked the review's claims against the same public Jira:
project in JRASERVERandstatus is Openreturn 400,key = "GPT-4"returns 200 with no issues,key = "JRASERVER-99999999"returns 400, andissue in "linkedIssues"("JRASERVER-1")runs.Unit tests cover the issue-key branch and its text retry (empty result and 400), the new-workspace retry, the fallback note at the hook level (set, cleared by the next search, dropped for a replaced search), the store's status check, the operator check, text building (including each punctuation case that broke), and the fallback: no retry on 401/403/429/5xx or network errors, retry failures reported as themselves, the JQL error kept when Jira also rejects the text, and Electron's wrapped IPC error format. With the fix reverted, 10+ of these tests fail on assertions.
Jira, Tasks page, new-workspace, store and i18n suites: 895 tests pass. Localization catalog, extraction and coverage checks pass, as does the changed-code quality gate.
Platforms: macOS locally. The change is renderer string logic with no platform-specific code. Not verified against Jira Cloud (same JQL grammar; the fallback relies only on the HTTP status, not error wording). The open-by-default Details for
=queries is covered by a component test, not a new screenshot.I manually tested these changes locally
Automated tests added/updated, or explained why not below
Review
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)