Skip to content

Commit ebae676

Browse files
srest2021claude
andcommitted
fix(autofix): Fall back to code mappings when preference has empty repos
read_preference_from_sentry_db now always returns a SeerProjectPreference instead of None, removing the has_configured_options gate that could incorrectly treat mechanically-written default options as "configured". _resolve_project_preference now checks preference.repositories instead of just truthiness — when repos are empty it falls through to the code mapping fallback while preserving the user's existing stopping point and handoff settings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a16fcfa commit ebae676

File tree

3 files changed

+23
-28
lines changed

3 files changed

+23
-28
lines changed

src/sentry/seer/autofix/autofix.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -643,9 +643,11 @@ def _resolve_project_preference(
643643
"""
644644
Resolve the Seer project preference for a project before triggering autofix.
645645
646-
If an existing preference is found in Seer, returns it.
647-
If not, creates one from fallback_repos.
646+
If an existing preference with repositories is found, returns it.
647+
Otherwise, creates one using fallback repos and preserves any existing
648+
stopping point / handoff settings.
648649
"""
650+
preference: SeerProjectPreference | None = None
649651
if features.has("organizations:seer-project-settings-read-from-sentry", organization):
650652
preference = read_preference_from_sentry_db(project)
651653
else:
@@ -658,16 +660,24 @@ def _resolve_project_preference(
658660
)
659661
return None
660662

661-
if preference:
663+
if preference and preference.repositories:
662664
return preference
663665

664-
default_stopping_point, default_handoff = get_org_default_seer_automation_handoff(organization)
666+
if preference:
667+
# Preference exists but has no repos —
668+
# keep the user's existing stopping point and handoff settings.
669+
stopping_point = preference.automated_run_stopping_point
670+
handoff = preference.automation_handoff
671+
else:
672+
# No preference at all — use org defaults.
673+
stopping_point, handoff = get_org_default_seer_automation_handoff(organization)
674+
665675
preference = SeerProjectPreference(
666676
organization_id=organization.id,
667677
project_id=project.id,
668678
repositories=fallback_repos,
669-
automated_run_stopping_point=default_stopping_point,
670-
automation_handoff=default_handoff,
679+
automated_run_stopping_point=stopping_point,
680+
automation_handoff=handoff,
671681
)
672682

673683
try:
@@ -684,7 +694,7 @@ def _resolve_project_preference(
684694
write_preference_to_sentry_db(project, preference)
685695
except Exception:
686696
logger.exception(
687-
"seer.write_preferences.resolve_project_preference.sentry_db_write_failed",
697+
"seer.resolve_project_preference.write_failed",
688698
extra={"project_id": project.id, "organization_id": organization.id},
689699
exc_info=True,
690700
)

src/sentry/seer/autofix/issue_summary.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -506,8 +506,7 @@ def get_automation_stopping_point(group: Group) -> AutofixStoppingPoint:
506506
fixability_stopping_point = _get_stopping_point_from_fixability(fixability_score)
507507

508508
if features.has("organizations:seer-project-settings-read-from-sentry", group.organization):
509-
preference = read_preference_from_sentry_db(group.project)
510-
user_preference = preference.automated_run_stopping_point if preference else None
509+
user_preference = read_preference_from_sentry_db(group.project).automated_run_stopping_point
511510
else:
512511
user_preference = _fetch_user_preference(group.project.id)
513512

src/sentry/seer/autofix/utils.py

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -734,7 +734,7 @@ def _build_automation_handoff(
734734
)
735735

736736

737-
def read_preference_from_sentry_db(project: Project) -> SeerProjectPreference | None:
737+
def read_preference_from_sentry_db(project: Project) -> SeerProjectPreference:
738738
"""Read a single project's Seer preferences from Sentry DB.
739739
740740
For now, should only be used under feature flag `organizations:seer-project-settings-read-from-sentry`."""
@@ -749,12 +749,6 @@ def read_preference_from_sentry_db(project: Project) -> SeerProjectPreference |
749749
if (repo_def := build_repo_definition_from_project_repo(project_repo)) is not None
750750
]
751751

752-
has_configured_options = any(
753-
ProjectOption.objects.isset(project, key) for key in SEER_PROJECT_PREFERENCE_OPTION_KEYS
754-
)
755-
if not repo_definitions and not has_configured_options:
756-
return None
757-
758752
return SeerProjectPreference(
759753
organization_id=project.organization_id,
760754
project_id=project.id,
@@ -767,7 +761,7 @@ def read_preference_from_sentry_db(project: Project) -> SeerProjectPreference |
767761

768762
def bulk_read_preferences_from_sentry_db(
769763
organization_id: int, project_ids: list[int]
770-
) -> dict[int, SeerProjectPreference | None]:
764+
) -> dict[int, SeerProjectPreference]:
771765
"""Bulk read Seer preferences from Sentry DB.
772766
773767
For now, should only be used under feature flag `organizations:seer-project-settings-read-from-sentry`."""
@@ -793,15 +787,8 @@ def bulk_read_preferences_from_sentry_db(
793787
for key in SEER_PROJECT_PREFERENCE_OPTION_KEYS
794788
}
795789

796-
result: dict[int, SeerProjectPreference | None] = {}
790+
result: dict[int, SeerProjectPreference] = {}
797791
for project in projects:
798-
has_configured_options = any(
799-
project_options[key][project.id] is not None
800-
for key in SEER_PROJECT_PREFERENCE_OPTION_KEYS
801-
)
802-
if project.id not in repo_definitions_by_project and not has_configured_options:
803-
result[project.id] = None
804-
continue
805792

806793
def _get_project_option(key: str) -> Any:
807794
value = project_options[key][project.id]
@@ -831,7 +818,7 @@ def bulk_read_preferences(
831818
Always returns ``dict[int, SeerProjectPreference | None]`` regardless of the
832819
underlying read path (Sentry DB or Seer API)."""
833820
if features.has("organizations:seer-project-settings-read-from-sentry", organization):
834-
return bulk_read_preferences_from_sentry_db(organization.id, project_ids)
821+
return bulk_read_preferences_from_sentry_db(organization.id, project_ids) # type: ignore[return-value]
835822

836823
raw = bulk_get_project_preferences(organization.id, project_ids)
837824
tuning_by_id = ProjectOption.objects.get_value_bulk_id(
@@ -887,8 +874,7 @@ def has_project_connected_repos(
887874

888875
has_repos = False
889876
if features.has("organizations:seer-project-settings-read-from-sentry", organization):
890-
preference = read_preference_from_sentry_db(project)
891-
has_repos = bool(preference and preference.repositories)
877+
has_repos = bool(read_preference_from_sentry_db(project).repositories)
892878
else:
893879
try:
894880
preference = get_project_seer_preferences(project.id).preference

0 commit comments

Comments
 (0)