From 7fa03c489eb8eca72afc3003ddc08b13604d996d Mon Sep 17 00:00:00 2001 From: Johannes Millan Date: Tue, 21 Apr 2026 12:35:12 +0200 Subject: [PATCH] fix(github-plugin): encode parentheses in search queries (#4913) encodeURIComponent leaves ( and ) intact per RFC 3986, but GitHub's search API treats them as grouping operators that must be percent-encoded, returning HTTP 422 on queries like "(author:@me OR assignee:@me)". The in-repo provider was fixed in 960330dd56 but the fix didn't carry over when GitHub moved to a plugin. Reapply via a small encodeGithubQuery helper used at both call sites. --- .../plugin-dev/github-issue-provider/src/plugin.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/plugin-dev/github-issue-provider/src/plugin.ts b/packages/plugin-dev/github-issue-provider/src/plugin.ts index 4bafb5df60..c8cb1af9ff 100644 --- a/packages/plugin-dev/github-issue-provider/src/plugin.ts +++ b/packages/plugin-dev/github-issue-provider/src/plugin.ts @@ -104,6 +104,13 @@ const isAuthOrNotFoundError = (err: unknown): boolean => { return false; }; +// GitHub's search API requires parentheses percent-encoded, but +// encodeURIComponent leaves them intact (they're unreserved per RFC 3986). +// Unencoded parens cause HTTP 422 on queries like "(author:@me OR assignee:@me)". +// See https://github.com/super-productivity/super-productivity/issues/4913 +const encodeGithubQuery = (query: string): string => + encodeURIComponent(query).replace(/\(/g, '%28').replace(/\)/g, '%29'); + PluginAPI.registerIssueProvider({ configFields: [ { @@ -162,7 +169,7 @@ PluginAPI.registerIssueProvider({ const hasTypeFilter = searchTerm.includes('is:issue') || searchTerm.includes('is:pull-request'); const typeFilter = hasTypeFilter ? '' : ' is:issue'; - const q = encodeURIComponent(`repo:${owner}/${repo}${typeFilter} ${searchTerm}`); + const q = encodeGithubQuery(`repo:${owner}/${repo}${typeFilter} ${searchTerm}`); const url = `${API_BASE}/search/issues?q=${q}&per_page=50`; const response = await http.get(url); return (response.items || []).map(mapSearchResult); @@ -262,7 +269,7 @@ PluginAPI.registerIssueProvider({ const cfg = config as unknown as GithubConfig; const { owner, repo } = parseRepo(cfg); const query = cfg.backlogQuery || 'sort:updated state:open assignee:@me'; - const q = encodeURIComponent(`repo:${owner}/${repo} is:issue ${query}`); + const q = encodeGithubQuery(`repo:${owner}/${repo} is:issue ${query}`); const url = `${API_BASE}/search/issues?q=${q}&per_page=50`; const response = await http.get(url); return (response.items || []).map(mapSearchResult);