Skip to content

fix(jira): search plain text when task search input isn't JQL - #22900

Merged
AmethystLiang merged 9 commits into
stablyai:mainfrom
AmoabaKelvin:AmoabaKelvin/fix-jira-partial-search
Oct 3, 2026
Merged

AmethystLiang merged 9 commits into
stablyai:mainfrom
AmoabaKelvin:AmoabaKelvin/fix-jira-partial-search

Conversation

@AmoabaKelvin

@AmoabaKelvin AmoabaKelvin commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator
Files Added Deleted Net
Test 8 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​525 $\color{#cf222e}{\Huge{\mathbf{−}}}$​7 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​518
Prod 20 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​295 $\color{#cf222e}{\Huge{\mathbf{−}}}$​113 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​182

ELI5

The Jira search box on the Tasks page only understood Jira's query language (JQL). Typing a normal word like s or test sent 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 test in 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:

  • Plain words (s, fix login bug) search issue text. An issue key (abc-12) narrows the list to exactly that issue, ready to click.
  • Key-shaped words that aren't keys (utf-8, sha-256, covid-19, or a mistyped key like ABC-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.
  • Everyday phrases with "in" or "is" (sign in, crash in terminal, login is slow) search issue text in one request, with no note.
  • Valid JQL runs unchanged, including WAS, CHANGED, custom fields and plugin functions.
  • Input Jira rejects as JQL (like login was slow or project = 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.
  • The Jira search in the new-workspace screen gets the same key-then-text fallback.
  • Sign-in, permission, rate-limit, server and network errors appear exactly as before.
  • The placeholder now reads "Search issues or enter JQL, e.g. project = ABC", and the empty-results message is "Try different search terms or JQL." (updated in all 6 locales).

How it works:

  1. Input shaped like an issue key (ABC-12) becomes key = "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.
  2. JQL has a fixed set of operators (= != < > ~ IN IS WAS CHANGED, or a leading ORDER BY), and Jira's grammar limits two of them: IN must be followed by a list or a function call, and IS by EMPTY or NULL. Input that doesn't fit can't be JQL, so it goes straight to a text search in one request.
  3. Everything else is sent to Jira as JQL first. Jira is the only reliable JQL parser, so we let it decide. If it answers 400, we retry the input as a text search. If the retry fails for a different reason (network, 429, 503), we show that error instead of blaming the query.
  4. Text searches become 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, so AND/OR/NOT are treated as ordinary words. Without this, a partially typed fix (login or say "hi would 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 like PROJ-1401 marked 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 jql string), and saved searches restore as before.

also use-task-page-jira-list-state.ts switches from a type assertion to Object.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 Done is valid JQL, while value is null may 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:
before-1-plain-text

After:
after-1-plain-text

Before:
before-3-prose-fallback

After:
after-3-prose-fallback

Testing

  • Reproduced the bug against Atlassian's public Jira Data Center (jira.atlassian.com): jql=test and jql=s return 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 x and C++ build each return results in one request. status WAS Done, assignee CHANGED and value is null run as JQL. project = NOPE falls 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 JRASERVER and status is Open return 400, key = "GPT-4" returns 200 with no issues, key = "JRASERVER-99999999" returns 400, and issue 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

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • SSH / remote: the query is built in the renderer, and the request to Jira is unchanged, so remote runtimes and mixed client/host versions behave the same.
  • Mobile: the shared text-search builder is plain string/regex code, safe for Hermes (mobile's JavaScript engine).
  • Performance: plain text uses one request, as before. Only input that could parse as JQL and still fails costs a second request, and a key-shaped search that matches no issue costs a second request.
  • Backwards compatibility: saved searches and valid JQL behave exactly as before.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: f300166c-0c44-48c0-b7ac-58e8018b4866

📥 Commits

Reviewing files that changed from the base of the PR and between 02e12bb931a6f960933dcc9769555e08ea77098c and 6b7b719bf8c338f2f86a8050979df0f02206a5dd.

📒 Files selected for processing (9)
  • mobile/src/terminal/terminal-live-text-commit.test.ts
  • src/renderer/src/components/use-task-page-jira-list-effects.test.ts
  • src/renderer/src/components/use-task-page-jira-list-effects.ts
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/fr.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/renderer/src/i18n/locales/fr.json
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/es.json

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.


📝 Walkthrough

Walkthrough

Jira 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 6b7b7

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 Review

Security architecture risk: 🔵 Low · up to 6b7b7

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
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — A rejected JQL expression may yield text matches broader than the attempted expression, but the inspected retry remains a Jira issue read through the existing source-scoped action. Jira-side permission enforcement was not verified.

Trust Boundaries and Controls

  • observed — The helper does not retry failures unless their message is recognized as Jira error 400. The store action continues to derive the source-specific read scope and selected site before dispatching the search.
  • observed — The marked public range in the effects test adds a fixture mock; it does not itself expose a production entrypoint.

Resilience and Maintainability Implications

  • observed — The effect clears prior query metadata before loading and ignores stale success and failure continuations after cleanup. The source establishes these guards, though delayed overlapping searches were not directly covered by a dedicated integration test in the inspected evidence.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The Jira search changes, shared builder, localized text, and related tests support issue #22265. The change to mobile/src/terminal/terminal-live-text-commit.test.ts only reformats an iterator spy in… Revert the formatting-only change in mobile/src/terminal/terminal-live-text-commit.test.ts, or provide a concrete Jira-related coding requirement that it implements.
Docstring Coverage ⚠️ Warning 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:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Issue #22265 requires free-text input to avoid invalid JQL errors on Jira Server and Data Center. mayBeJql classifies input without JQL operators as text, and buildJiraTextSearchJql creates valid …
Title check ✅ Passed The title clearly and concisely describes the main change: supporting plain-text Jira searches when the input is not JQL.
Description check ✅ Passed The description is complete and aligned with the template. It explains the user impact, implementation, rationale, linked issue, visual proof, testing, compatibility considerations, and checklist stat…
Full details: Out of Scope Changes check

Explanation

The Jira search changes, shared builder, localized text, and related tests support issue #22265. The change to mobile/src/terminal/terminal-live-text-commit.test.ts only reformats an iterator spy in an unrelated terminal performance test. The summary reports no behavior or assertion change for that file, so it has no demonstrated connection to the Jira objective.

Full details: Docstring Coverage

Explanation

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.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

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.ts
  • src/renderer/src/components/task-page-jira-load-state.test.ts
  • src/renderer/src/components/task-page-jira-load-state.ts
  • src/renderer/src/components/task-page-jira-search.test.ts
  • src/renderer/src/components/task-page-jira-search.ts
  • src/renderer/src/components/task-page-jira-text-fallback-notice.test.tsx
  • src/renderer/src/components/task-page-jira-text-fallback-notice.tsx
  • src/renderer/src/components/task-page/jira/Content.tsx
  • src/renderer/src/components/task-page/jira/Filters.tsx
  • src/renderer/src/components/use-task-page-jira-list-effects.ts
  • src/renderer/src/components/use-task-page-jira-list-state.ts
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/fr.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/shared/jira-search-input-jql.test.ts
  • src/shared/jira-search-input-jql.ts
  • src/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.

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (2)

🟡 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 win

Open 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 win

Mount the status region before adding the fallback message.

Content currently mounts a populated role="status" element only after the fallback result arrives. A status live 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.tsx
  • src/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.

@pullfrog pullfrog 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.

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 mayBeJql decides "could this be JQL?" from a closed operator set; buildJiraTextSearchJql lowercases, strips Lucene syntax, and emits text ~ "…*" or key = "…".
  • Task-page search orchestration — searchTaskPageJiraIssues skips 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 TaskPageJiraTextFallbackNotice renders "Showing text matches" with Jira's rejection reason collapsed behind a Details control.
  • State/effects plumbing — jiraJqlRejection threaded through use-task-page-jira-list-state.ts and use-task-page-jira-list-effects.ts into jira/Content.tsx; the list-state hook swaps its as assertion for Object.assign.
  • New-workspace reuse — buildJiraIssueSearchJql now 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.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread src/shared/jira-search-input-jql.ts Resolved

@pullfrog pullfrog 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.

ℹ️ 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 — TaskPageJiraTextFallbackNotice now carries role="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".

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes

  • Wildcard dropped for punctuation-final text searches — buildJiraTextSearchJql now appends the trailing * only when the last word matches /^[\p{L}\p{N}']+$/u, so inputs like fix login., C#, 100%, and $5 are 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 — TaskPageJiraTextFallbackNotice now always renders its role="status" wrapper and renders the body in a key-ed child, and Content.tsx always 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.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes (delta since prior pullfrog review at e11e9be)

  • Hyphenated operator words now stay plain text — JQL_OPERATOR_PATTERN swaps \b(?:in|is|was|changed)\b for (?<![\w-])(?:in|is|was|changed)(?![\w-]), so sign-in page and built-in was- no longer look like JQL and skip the wasted JQL round-trip plus the fallback notice; jira-search-input-jql.test.ts adds both cases.
  • Fallback notice guard simplified — Content.tsx drops the redundant !jiraError from the notice's reason prop while keeping the credential-error guard. Neutral: the effect clears jiraJqlRejection at the start of every run and sets jiraError only 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.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes (delta since prior pullfrog review at 02e12bb)

  • Merged origin/main into the PR branch — the only new commit; it integrates main's new force option on the Jira list/search path without dropping the PR's text-search wrapper.
  • force threaded through the text-search wrapper — use-task-page-jira-list-effects.ts now passes force into searchJiraIssues inside the searchTaskPageJiraIssues closure, 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 refresh asserts the nonce change delivers searchJiraIssues('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.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@AmethystLiang
AmethystLiang requested review from AmethystLiang and removed request for brennanb2025 September 30, 2026 21:18
@AmethystLiang

Copy link
Copy Markdown
Contributor

Review: right approach, three things to tighten before merge

The 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. in / is send everyday searches down the JQL path (should fix)

JQL_OPERATOR_PATTERN treats a bare in or is as a JQL operator. So sign in, log in page, crash in terminal, login is slow and plug in all:

  1. send a JQL request that's guaranteed to fail,
  2. send a second request, and then
  3. show "Jira couldn't run this search as JQL" to someone who never typed JQL.

The results are correct. But some of the most common searches cost two requests and show a confusing note.

Jira's grammar is narrower than the pattern. IN needs a list or a function, and IS needs EMPTY or NULL:

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/i

I 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-8 and covid-19 become key = "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'". searchTaskPageJiraIssues rethrows 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.

@AmethystLiang

Copy link
Copy Markdown
Contributor

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:

  1. Read the diff, then trace the whole request path, not just the changed files. The path goes from the Tasks page effect, to the store's searchJiraIssues (caching and in-flight request sharing), to the runtime client (local IPC vs. remote runtime RPC), to main's searchIssues (the multi-site fan-out, and which site errors get swallowed), to toIssueSearchFailureError (where the Error 400: prefix comes from). This is where the IPC-prefix nit came from. It also confirmed that on older remote hosts, which don't add the prefix, the fallback simply never triggers and behavior stays as it was before this PR.
  2. Ran the PR's tests (6 files, 145 pass). This confirms the behavior the tests describe; it doesn't find new bugs.
  3. Turned each claim in the PR description into a question and asked a real Jira. jira.atlassian.com has a public Data Center search API, so anonymous read-only requests show exactly how Jira treats a given query:
    curl -s -G https://jira.atlassian.com/rest/api/2/search \
      --data-urlencode 'jql=status is Open' \
      --data-urlencode maxResults=1 --data-urlencode fields=key
    • "Input with operator words might be JQL": project in JRASERVER and status is Open are rejected by Jira's grammar → finding 1.
    • "Invalid JQL falls back to a text search": ran the actual fallback queries and counted the results (49 and 10) → finding 2.
    • "Plain words search issue text; keys open directly": tried key = "GPT-4" (200, empty) and a missing key (400) → finding 3.
  4. Ran common search phrases through the JQL-detection pattern in node (sign in, crash in terminal, login is slow, …) to see which ones take the JQL path.
  5. Checked the suggested fix against the PR's own test cases. Before suggesting the tighter pattern, I ran it on every JQL case in jira-search-input-jql.test.ts plus extra function and IN( forms. None of them stopped being detected as JQL.
  6. Did a second pass to argue against each finding. Anything that didn't hold up was downgraded, narrowed or dropped:
    • Finding 1 was first written as a correctness bug. The results are actually correct, so it's now about the extra request and the misleading note.
    • Finding 2 first suggested bringing back the error for any operator input. That would break plain searches like ~/.zshrc or <br>, so it's narrowed to = and !=.
    • The banner text in finding 3 was corrected after re-reading getIssueSearchErrorSummary.
    • A point about caching rejected queries was dropped as too minor.

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.

@greptile-apps

greptile-apps Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium risk] Adds fallback plain-text search when Jira JQL queries fail.

The PR appears safe to merge based on the reviewed changes.

Summary

The PR adds plain-text and issue-key search alongside JQL on the Jira Tasks page, shares key-then-text behavior with new-workspace search, and updates fallback messaging and tests.

Reviews (3) · Last reviewed commit: "fix(jira): require IN to be its own word..."

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes (delta since prior pullfrog review at 6b7b719)

  • Split the Jira JQL builders — buildJiraTextSearchJql is now composed from the new buildJiraIssueKeyJql (key shape → key = "…", else null) and buildJiraTextMatchJql (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 and new-workspace buildJiraIssueSearchJql are unaffected.
  • Key-shaped input retried as text — searchTaskPageJiraIssues now looks up key = "ABC-12" first and, when no issue matches and the input still yields searchable words, retries the same input as text ~ "…*" 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" then text ~ "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.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

AmoabaKelvin and others added 8 commits October 1, 2026 14:17
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.
@AmoabaKelvin
AmoabaKelvin force-pushed the AmoabaKelvin/fix-jira-partial-search branch from fb23e02 to 1b3d989 Compare October 1, 2026 14:19
@AmoabaKelvin

Copy link
Copy Markdown
Collaborator Author

@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. in / is. Fixed with your pattern, plus one addition: a quoted function name, since issue in "linkedIssues"("JRASERVER-1") is valid JQL and the unquoted-only form missed it. sign in, crash in terminal, login is slow and this is broken now go straight to text in one request.

2. JQL typos. When the rejected input contains =, Details starts open, so Jira's reason is visible without a click. Other rejected searches keep the collapsed note.

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, searchJiraIssuesByKeyOrText, and the new-workspace Jira search calls it too, so it no longer stops at an empty key lookup either. The description now says a key narrows the list to that issue.

Nit. One parser now (src/shared/jira-status-error.ts), unanchored, used by the fallback and the banner.

One more thing turned up while checking the key path: the store decided "sign-in failure" by looking for 401 anywhere in the error text, so searching a missing key like PROJ-1401 marked Jira as disconnected. It now uses the status code when the message has one, through the same parser.

Left as is: was and changed still let Jira decide, as you noted.

@pullfrog pullfrog 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.

ℹ️ 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.ts owns the "try key = "…", fall back to text ~ "…" when the key lookup returns nothing or a 400" logic; both the task page and new-workspace now call it.
  • IN/IS classified by grammar — JQL_OPERATOR_PATTERN now accepts in only before a list/function call and is only before EMPTY/NULL, so sign in, crash in terminal and login is slow skip the JQL request entirely.
  • JQL rejection carries likelyTypo — the reason string became TaskPageJiraJqlRejection, and the fallback notice opens Details by default when the rejected input contains =.
  • Status parsing centralized — new parseJiraStatusError reads Error <status>: <details> for the fallback detection, the error banner and looksLikeJiraAuthError, so a 400 that merely mentions 401 (e.g. key 'PROJ-1401') no longer marks Jira disconnected.
  • New-workspace query handoff — getJiraIssueSearchQuery replaces buildJiraIssueSearchJql and returns the raw trimmed query, so the key-then-text fallback applies on that path too.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread src/shared/jira-search-input-jql.ts Resolved
`input (raw)` and `init()` read as `in` + a function call and took the JQL path.

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes (delta since the prior pullfrog review at 1b3d989)

  • IN word boundary tightened in the JQL classifier — JQL_OPERATOR_PATTERN now requires whitespace before an unquoted function name ((?<![\w-])in(?:\s+[a-z_][\w.]*|\s*"[^"]+"|\s*'[^']+')?\s*\(), so input (raw), install (macos) and init() no longer glue onto in and are correctly classified as text; quoted function names may still follow directly since the quote ends the word. Adds those three as text cases plus status 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).

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@AmethystLiang
AmethystLiang merged commit ee84608 into stablyai:main Oct 3, 2026
35 checks passed
tisohjung added a commit to tisohjung/orca that referenced this pull request Oct 4, 2026
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Jira issue search treats partial input as invalid JQL on on-prem Jira

3 participants