Skip to content

Commit 3e4be5d

Browse files
antonsyndclaude
andcommitted
feat(dogfood): add identical-error early termination in retry loops
When the same compiler error recurs on consecutive retry attempts, stop early instead of burning all 3 attempts. This saves ~3 minutes and avoids pointless token spend on unresolvable compiler bugs. Adds _errors_are_equivalent() helper that normalizes error text (strips file:line:col refs, collapses whitespace) and applies it at both pre-validation and semantic validation retry points in both single-file and multi-file code generation loops. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2390ecc commit 3e4be5d

1 file changed

Lines changed: 79 additions & 0 deletions

File tree

build_tools/sharpy_dogfood/orchestrator.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,23 @@ def _is_internal_compiler_error(error_message: str) -> bool:
9090
return bool(_INTERNAL_COMPILER_ERROR_PATTERN.search(error_message))
9191

9292

93+
def _errors_are_equivalent(err1: Optional[str], err2: Optional[str]) -> bool:
94+
"""Check if two validation errors are essentially identical.
95+
96+
Compares normalized error text to detect when the AI is producing
97+
the same failing code across retry attempts (likely a compiler bug,
98+
not a fixable code issue).
99+
"""
100+
if not err1 or not err2:
101+
return False
102+
# Normalize: strip whitespace, collapse spaces, remove file:line:col refs
103+
def normalize(s: str) -> str:
104+
s = re.sub(r'-->\s*\S+:\d+:\d+', '', s) # Remove file:line:col
105+
s = re.sub(r'\s+', ' ', s).strip()
106+
return s
107+
return normalize(err1) == normalize(err2)
108+
109+
93110
class IterationStatus(Enum):
94111
"""Status of a dogfooding iteration."""
95112

@@ -1066,6 +1083,7 @@ async def _generate_and_validate_code(
10661083
last_code: Optional[str] = None
10671084
last_raw_output: Optional[str] = None
10681085
last_error: Optional[str] = None
1086+
previous_error: Optional[str] = None
10691087
total_duration = 0.0
10701088
backend_used: Optional[str] = None
10711089

@@ -1183,6 +1201,21 @@ async def _generate_and_validate_code(
11831201
f" Pre-validation failed: {prevalidation_error}", file=sys.stderr
11841202
)
11851203
last_error = f"Pre-validation error: {prevalidation_error}"
1204+
if _errors_are_equivalent(last_error, previous_error):
1205+
print(
1206+
f" Same error on consecutive attempts — likely a compiler limitation. Stopping retries.",
1207+
file=sys.stderr,
1208+
)
1209+
return GenerationResult(
1210+
success=False,
1211+
code=code,
1212+
expected_output=extract_expected_output_from_response(gen_result.output),
1213+
skip_reason=f"Repeated identical error (likely compiler bug): {prevalidation_error}",
1214+
backend_used=backend_used,
1215+
generation_duration=total_duration,
1216+
attempts=attempt,
1217+
)
1218+
previous_error = last_error
11861219
if attempt < max_attempts:
11871220
print(
11881221
f" Will retry with feedback ({attempt}/{max_attempts})...",
@@ -1225,6 +1258,21 @@ async def _generate_and_validate_code(
12251258
file=sys.stderr,
12261259
)
12271260
last_error = f"Sharpy compiler error: {semantic_error}"
1261+
if _errors_are_equivalent(last_error, previous_error):
1262+
print(
1263+
f" Same error on consecutive attempts — likely a compiler limitation. Stopping retries.",
1264+
file=sys.stderr,
1265+
)
1266+
return GenerationResult(
1267+
success=False,
1268+
code=code,
1269+
expected_output=extract_expected_output_from_response(gen_result.output),
1270+
skip_reason=f"Repeated identical compiler error (likely compiler bug): {semantic_error}",
1271+
backend_used=backend_used,
1272+
generation_duration=total_duration,
1273+
attempts=attempt,
1274+
)
1275+
previous_error = last_error
12281276
if attempt < max_attempts:
12291277
print(
12301278
f" Will retry with feedback ({attempt}/{max_attempts})...",
@@ -1355,6 +1403,7 @@ async def _generate_and_validate_multifile_code(
13551403
max_attempts = self.config.max_regeneration_attempts
13561404
last_files: Optional[dict[str, str]] = None
13571405
last_error: Optional[str] = None
1406+
previous_error: Optional[str] = None
13581407
total_duration = 0.0
13591408
backend_used: Optional[str] = None
13601409

@@ -1486,6 +1535,21 @@ async def _generate_and_validate_multifile_code(
14861535
break
14871536

14881537
if prevalidation_failed:
1538+
if _errors_are_equivalent(last_error, previous_error):
1539+
print(
1540+
f" Same error on consecutive attempts — likely a compiler limitation. Stopping retries.",
1541+
file=sys.stderr,
1542+
)
1543+
return MultifileGenerationResult(
1544+
success=False,
1545+
files=files,
1546+
expected_output=extract_expected_output_from_multifile(files),
1547+
skip_reason=f"Repeated identical error (likely compiler bug): {last_error}",
1548+
backend_used=backend_used,
1549+
generation_duration=total_duration,
1550+
attempts=attempt,
1551+
)
1552+
previous_error = last_error
14891553
if attempt < max_attempts:
14901554
print(
14911555
f" Will retry with feedback ({attempt}/{max_attempts})...",
@@ -1528,6 +1592,21 @@ async def _generate_and_validate_multifile_code(
15281592
file=sys.stderr,
15291593
)
15301594
last_error = f"Sharpy compiler error: {semantic_error}"
1595+
if _errors_are_equivalent(last_error, previous_error):
1596+
print(
1597+
f" Same error on consecutive attempts — likely a compiler limitation. Stopping retries.",
1598+
file=sys.stderr,
1599+
)
1600+
return MultifileGenerationResult(
1601+
success=False,
1602+
files=files,
1603+
expected_output=extract_expected_output_from_multifile(files),
1604+
skip_reason=f"Repeated identical compiler error (likely compiler bug): {semantic_error}",
1605+
backend_used=backend_used,
1606+
generation_duration=total_duration,
1607+
attempts=attempt,
1608+
)
1609+
previous_error = last_error
15311610
if attempt < max_attempts:
15321611
print(
15331612
f" Will retry with feedback ({attempt}/{max_attempts})...",

0 commit comments

Comments
 (0)