@@ -132,12 +132,66 @@ async function tryResolveFromAlias(
132132 return { org : projectEntry . orgSlug , issue } ;
133133}
134134
135+ /**
136+ * Fallback for when the fast shortid fan-out found no matches.
137+ * Uses findProjectsBySlug to give a precise error ("project not found" vs
138+ * "issue not found") and retries the issue lookup on transient failures.
139+ */
140+ async function resolveProjectSearchFallback (
141+ projectSlug : string ,
142+ suffix : string ,
143+ commandHint : string
144+ ) : Promise < StrictResolvedIssue > {
145+ const { projects } = await findProjectsBySlug ( projectSlug . toLowerCase ( ) ) ;
146+
147+ if ( projects . length === 0 ) {
148+ throw new ResolutionError (
149+ `Project '${ projectSlug } '` ,
150+ "not found" ,
151+ commandHint ,
152+ [ "No project with this slug found in any accessible organization" ]
153+ ) ;
154+ }
155+
156+ if ( projects . length > 1 ) {
157+ const orgList = projects . map ( ( p ) => p . orgSlug ) . join ( ", " ) ;
158+ throw new ResolutionError (
159+ `Project '${ projectSlug } '` ,
160+ "is ambiguous" ,
161+ commandHint ,
162+ [
163+ `Found in: ${ orgList } ` ,
164+ `Specify the org: sentry issue ... <org>/${ projectSlug } -${ suffix } ` ,
165+ ]
166+ ) ;
167+ }
168+
169+ // Project exists — retry the issue lookup. The fast path may have failed
170+ // due to a transient error (5xx, timeout); retrying here either succeeds
171+ // or propagates the real error to the user.
172+ const matchedProject = projects [ 0 ] ;
173+ const matchedOrg = matchedProject ?. orgSlug ;
174+ if ( matchedOrg && matchedProject ) {
175+ const retryShortId = expandToFullShortId ( suffix , matchedProject . slug ) ;
176+ const issue = await getIssueByShortId ( matchedOrg , retryShortId ) ;
177+ return { org : matchedOrg , issue } ;
178+ }
179+
180+ throw new ResolutionError (
181+ `Project '${ projectSlug } '` ,
182+ "not found" ,
183+ commandHint
184+ ) ;
185+ }
186+
135187/**
136188 * Resolve project-search type: search for project across orgs, then fetch issue.
137189 *
138190 * Resolution order:
139191 * 1. Try alias cache (fast, local)
140- * 2. Search for project across orgs via API
192+ * 2. Check DSN detection cache
193+ * 3. Try shortid endpoint directly across all orgs (fast path)
194+ * 4. Fall back to findProjectsBySlug for precise error messages
141195 *
142196 * @param projectSlug - Project slug to search for
143197 * @param suffix - Issue suffix (uppercase)
@@ -234,48 +288,9 @@ async function resolveProjectSearch(
234288 }
235289 }
236290
237- // 4. All orgs returned 404 — fall back to findProjectsBySlug for a
238- // precise error: "project not found" vs "issue not found in project".
239- const { projects } = await findProjectsBySlug ( projectSlug . toLowerCase ( ) ) ;
240-
241- if ( projects . length === 0 ) {
242- throw new ResolutionError (
243- `Project '${ projectSlug } '` ,
244- "not found" ,
245- commandHint ,
246- [ "No project with this slug found in any accessible organization" ]
247- ) ;
248- }
249-
250- if ( projects . length > 1 ) {
251- const orgList = projects . map ( ( p ) => p . orgSlug ) . join ( ", " ) ;
252- throw new ResolutionError (
253- `Project '${ projectSlug } '` ,
254- "is ambiguous" ,
255- commandHint ,
256- [
257- `Found in: ${ orgList } ` ,
258- `Specify the org: sentry issue ... <org>/${ projectSlug } -${ suffix } ` ,
259- ]
260- ) ;
261- }
262-
263- // Project exists — retry the issue lookup. The fast path may have failed
264- // due to a transient error (5xx, timeout); retrying here either succeeds
265- // or propagates the real error to the user.
266- const matchedProject = projects [ 0 ] ;
267- const matchedOrg = matchedProject ?. orgSlug ;
268- if ( matchedOrg && matchedProject ) {
269- const retryShortId = expandToFullShortId ( suffix , matchedProject . slug ) ;
270- const issue = await getIssueByShortId ( matchedOrg , retryShortId ) ;
271- return { org : matchedOrg , issue } ;
272- }
273-
274- throw new ResolutionError (
275- `Project '${ projectSlug } '` ,
276- "not found" ,
277- commandHint
278- ) ;
291+ // 4. Fall back to findProjectsBySlug for precise error messages
292+ // and retry the issue lookup (handles transient failures).
293+ return resolveProjectSearchFallback ( projectSlug , suffix , commandHint ) ;
279294}
280295
281296/**
0 commit comments