Skip to content

fix(resolve): fuzzy auto-recovery for project slug resolution#728

Merged
BYK merged 3 commits intomainfrom
fix/project-slug-fuzzy-recovery
Apr 13, 2026
Merged

fix(resolve): fuzzy auto-recovery for project slug resolution#728
BYK merged 3 commits intomainfrom
fix/project-slug-fuzzy-recovery

Conversation

@BYK
Copy link
Copy Markdown
Member

@BYK BYK commented Apr 13, 2026

Summary

  • When project-search finds accessible orgs but no exact project match, fuzzy-match against all projects before throwing ResolutionError
  • If exactly one similar project found (prefix/substring/close edit distance), auto-recover with log.warn() nudge instead of failing
  • If multiple similar projects found, add them as suggestions in the ResolutionError
  • Applied at all 3 project-search error sites: resolveProjectBySlug, resolveOrgProjectTarget, handleProjectSearch
  • Fuzzy lookup only runs on error path — zero impact on happy-path performance

Sentry 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

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).
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 13, 2026

Semver Impact of This PR

🟢 Patch (bug fixes)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

  • (cli) Add sentry cli defaults command for persistent settings by BYK in #721

Bug Fixes 🐛

Resolve

  • Fuzzy auto-recovery for project slug resolution by BYK in #728
  • Fuzzy auto-recovery for project slug resolution by BYK in #728

Upgrade

  • Detect npm install method from node_modules path by BYK in #723
  • Add shell option on Windows for .cmd package managers by BYK in #722

Other

  • (dashboard) Remove overly restrictive dataset-display cross-validation by BYK in #720
  • (init) Remove JSON minification that breaks edit-based codemods by betegon in #719
  • (issue) Support share issue URLs by BYK in #718
  • (issue-list) Auto-correct AND and reject OR in --query to prevent 400 by BYK in #727
  • (telemetry) Rename isClientApiError to isUserApiError and exclude 400 by BYK in #729

Internal Changes 🔧

  • Regenerate skill files by github-actions[bot] in ca16b2ff

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 13, 2026

PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://cli.sentry.dev/_preview/pr-728/

Built to branch gh-pages at 2026-04-13 14:24 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 13, 2026

Codecov Results 📊

134 passed | Total: 134 | Pass Rate: 100% | Execution Time: 0ms

📊 Comparison with Base Branch

Metric Change
Total Tests
Passed Tests
Failed Tests
Skipped Tests

✨ No test changes detected

All tests are passing successfully.

❌ Patch coverage is 53.77%. Project has 1644 uncovered lines.
❌ Project coverage is 95.21%. Comparing base (base) to head (head).

Files with missing lines (2)
File Patch % Lines
src/lib/resolve-target.ts 45.57% ⚠️ 43 Missing
src/lib/org-list.ts 77.78% ⚠️ 6 Missing
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

Comment thread src/lib/org-list.ts Outdated
Comment thread src/lib/resolve-target.ts
Comment thread src/lib/org-list.ts
BYK added 2 commits April 13, 2026 14:12
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
Comment thread src/lib/resolve-target.ts
Comment on lines +1283 to +1293
const proj = await withAuthGuard(() =>
getProject(recovered.org, recovered.project)
);
if (proj.ok) {
return withTelemetryContext({
org: recovered.org,
project: recovered.project,
projectData: proj.value,
});
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/lib/org-list.ts
Comment on lines +746 to +754
if (!_isRecoveryAttempt) {
const correctedSlug = await tryFuzzyRecoveryForList(
projectSlug,
orgs,
config.commandPrefix
);
if (correctedSlug) {
return handleProjectSearch(config, correctedSlug, options, true);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in PR #732tryFuzzyRecoveryForList 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.

Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

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

Comment thread src/lib/org-list.ts
if (correctedSlug) {
return handleProjectSearch(config, correctedSlug, options, true);
}
}
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.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c1a36ff. Configure here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in PR #732tryFuzzyProjectRecovery 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.

@BYK BYK merged commit 26a48b8 into main Apr 13, 2026
26 checks passed
@BYK BYK deleted the fix/project-slug-fuzzy-recovery branch April 13, 2026 14:30
BYK added a commit that referenced this pull request Apr 13, 2026
… 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.
BYK added a commit that referenced this pull request Apr 13, 2026
… 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
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.

1 participant