fix(resolve): fuzzy auto-recovery for project slug resolution#728
Conversation
When project-search finds accessible orgs but no exact project match, attempt fuzzy matching against all projects in those orgs before throwing ResolutionError. If exactly one similar project is found (prefix, substring, or close Levenshtein match), auto-recover with a log.warn() nudge instead of failing. Applied at all 3 project-search error sites: - resolveProjectBySlug (view commands) - resolveOrgProjectTarget (trace/span/log list) - handleProjectSearch (issue/project/team list) The fuzzy lookup only runs on the error path (project not found), so happy-path performance is unaffected. listProjects results are cached. Resolves CLI-C0, CLI-R8, CLI-Z8, CLI-XE, CLI-MA (200+ affected users).
Semver Impact of This PR🟢 Patch (bug fixes) 📋 Changelog PreviewThis is how your changes will appear in the changelog. New Features ✨
Bug Fixes 🐛Resolve
Upgrade
Other
Internal Changes 🔧
🤖 This preview updates automatically when you update the PR. |
|
Codecov Results 📊✅ 134 passed | Total: 134 | Pass Rate: 100% | Execution Time: 0ms 📊 Comparison with Base Branch
✨ No test changes detected All tests are passing successfully. ❌ Patch coverage is 53.77%. Project has 1644 uncovered lines. Files with missing lines (2)
Coverage diff@@ Coverage Diff @@
## main #PR +/-##
==========================================
- Coverage 95.35% 95.21% -0.14%
==========================================
Files 234 234 —
Lines 34209 34314 +105
Branches 0 0 —
==========================================
+ Hits 32618 32670 +52
- Misses 1591 1644 +53
- Partials 0 0 —Generated by Codecov Action |
Address Seer review: prevent potential infinite loop when fuzzy recovery suggests a project that also fails exact lookup. The _isRecoveryAttempt flag ensures fuzzy recovery only runs once per call chain.
…return Address Cursor Bugbot reviews: - Deduplicate project slugs before fuzzyMatch to avoid inflated results when the same slug exists in multiple orgs - Move fuzzy recovery before the flags.json early return so JSON mode consumers also benefit from auto-recovery
| const proj = await withAuthGuard(() => | ||
| getProject(recovered.org, recovered.project) | ||
| ); | ||
| if (proj.ok) { | ||
| return withTelemetryContext({ | ||
| org: recovered.org, | ||
| project: recovered.project, | ||
| projectData: proj.value, | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
Bug: After fuzzy project recovery, a subsequent failure to fetch the recovered project causes a misleading error message that references the original, unfound project slug.
Severity: MEDIUM
Suggested Fix
Add an else block after the if (proj.ok) check within the if (recovered) block. This else block should handle the failure of the getProject call by throwing a more specific error, clarifying that the recovered project could not be fetched, rather than falling through to the generic error message.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: src/lib/resolve-target.ts#L1282-L1293
Potential issue: In `resolveProjectBySlug`, when fuzzy project recovery finds a single
match, it logs a warning that it will use the recovered project. However, if the
subsequent `getProject` call for this recovered project fails (e.g., due to a race
condition where permissions change or the project is deleted), the logic silently falls
through. It then throws a generic `ResolutionError` using the original, unfound project
slug. This results in a confusing user experience where the user is first told a similar
project will be used, and then immediately receives an error that the original project
was not found.
There was a problem hiding this comment.
Addressed in PR #732 — refactored tryFuzzyProjectRecovery to return a discriminated result type (match | suggestions | none) instead of logging internally. Callers now only log.warn() after confirming the recovered project is accessible via getProject. If the fetch fails, it falls through silently to the original error.
| if (!_isRecoveryAttempt) { | ||
| const correctedSlug = await tryFuzzyRecoveryForList( | ||
| projectSlug, | ||
| orgs, | ||
| config.commandPrefix | ||
| ); | ||
| if (correctedSlug) { | ||
| return handleProjectSearch(config, correctedSlug, options, true); | ||
| } |
There was a problem hiding this comment.
Bug: If a fuzzy-recovered project lookup fails in handleProjectSearch, the resulting error message incorrectly references the recovered slug, which the user never typed, causing confusion.
Severity: MEDIUM
Suggested Fix
Wrap the recursive call to handleProjectSearch in a try...catch block. If an error is caught, re-throw a ResolutionError that preserves the context of the original projectSlug the user provided, ensuring the final error message is relevant to the user's input.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: src/lib/org-list.ts#L746-L754
Potential issue: In `handleProjectSearch`, when a user provides a project slug that has
a fuzzy match, the function logs a warning and recursively calls itself with the
corrected slug. If this recursive call fails (e.g., due to a transient API error, rate
limit, or race condition), the subsequent `ResolutionError` is thrown using the
corrected slug, not the one the user originally typed. This is confusing for the user,
who sees a warning about using a corrected project name and then an error that this
corrected name (which they never entered) was not found.
There was a problem hiding this comment.
Addressed in PR #732 — tryFuzzyRecoveryForList now returns a discriminated type instead of throwing. If the recursive call fails, the error references the original slug (projectSlug parameter), not the recovered one, since the recursion guard (_isRecoveryAttempt=true) prevents further recovery attempts on the re-entered call.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c1a36ff. Configure here.
| if (correctedSlug) { | ||
| return handleProjectSearch(config, correctedSlug, options, true); | ||
| } | ||
| } |
There was a problem hiding this comment.
Fuzzy recovery throws before JSON mode early return
Medium Severity
When multiple similar projects are found, tryFuzzyProjectRecovery throws a ResolutionError (via errorFactory). In handleProjectSearch, this throw occurs before the flags.json early-return check at line 757 that returns { items: [] }. This means JSON consumers receive an unhandled error instead of the expected empty-items response, breaking the contract that JSON mode degrades gracefully on "not found."
Additional Locations (1)
Reviewed by Cursor Bugbot for commit c1a36ff. Configure here.
There was a problem hiding this comment.
Addressed in PR #732 — tryFuzzyProjectRecovery no longer throws for multiple matches. It returns { kind: 'suggestions', suggestions } and the caller passes suggestions to ResolutionError only in human mode. JSON mode falls through to return { items: [] } correctly.
… recovery
- Refactor tryFuzzyProjectRecovery to return discriminated result type
(match | suggestions | none) instead of logging/throwing internally.
Callers now own side effects — log.warn only after confirming success.
- Fix Seer: after fuzzy recovery, getProject failure no longer shows
misleading error referencing the original unfound slug.
- Fix Seer: recursive handleProjectSearch failure no longer references
the recovered slug the user never typed.
- Fix Cursor: multiple fuzzy matches no longer throw before JSON early
return — JSON consumers get { items: [] } instead of exception.
- Add 8 unit tests for tryFuzzyProjectRecovery covering single match,
multiple matches, no matches, empty orgs, API failures, cross-org
search, slug deduplication, and partial org failures.
Follow-up to PR #728.
… recovery (#732) ## Summary Follow-up to PR #728 — addresses 3 missed review comments and adds unit tests for patch coverage. ### Review fixes - **Seer (misleading error after recovery)**: `tryFuzzyProjectRecovery` now returns a discriminated result type (`match` | `suggestions` | `none`) instead of logging/throwing. Callers only warn after confirming the recovered project is accessible. - **Seer (recursive call references wrong slug)**: If the recursive `handleProjectSearch` call fails, the error references the original slug the user typed, not the recovered one. - **Cursor (JSON mode gets exception)**: When multiple fuzzy matches are found, `suggestions` are returned instead of thrown — callers pass them to `ResolutionError` for human mode or return `{ items: [] }` for JSON mode. ### Coverage - 8 new unit tests for `tryFuzzyProjectRecovery` covering: single match, multiple matches, no matches, empty orgs, API failures, cross-org search, slug deduplication, partial org failures


Summary
log.warn()nudge instead of failingresolveProjectBySlug,resolveOrgProjectTarget,handleProjectSearchSentry Issues Resolved
Resolves 5 ResolutionError issues where directory-name inference found an org but the project slug didn't match any project exactly. Affects 200+ users across these issue IDs: CLI-C0, CLI-R8, CLI-Z8, CLI-XE, CLI-MA.
Example
Before:
Error: Project 'myapp' not found.After:
⚠ Project 'myapp' not found. Using similar project 'myapp-frontend' in org 'myorg'.→ continues with results