#1491·SWE-agent

Chooser swallows cost-limit exceptions (bare except Exception) and crashes with UnboundLocalError, defeating the documented abort

Author: EvolveAegisCreated Aug 5, 2026Updated Sep 17, 2026

Describe the bug

Chooser.choose() in sweagent/agent/reviewer.py wraps self.model.query() in a bare except Exception (lines 359-364). TotalCostLimitExceededError and InstanceCostLimitExceededError are subclasses of CostLimitExceededError (sweagent/exceptions.py:31-44), so a cost-limit breach raised from a chooser query is absorbed instead of propagating.

The main run loop explicitly treats this exception class as fatal: sweagent/agent/agents.py:1180-1186 re-raises TotalCostLimitExceededError and exits with exit_cost for other cost-limit errors. A comment near the parallel catch in agents.py:342-344 states the intent: "Need to make sure that this error causes everything to stop". The chooser path is the one place where this intent is defeated: the swallowed exception means the chooser never signals the breach, and the agent continues instead of stopping.

There is a second, unconditional defect on the same path: after the exception is swallowed, the function falls through to return ChooserOutput(chosen_idx=..., response=response, ...) at line 371, where response was never assigned. Any exception from self.model.query() therefore produces an UnboundLocalError instead of the intended graceful fallback. The caller agents.py:371-373 catches that with a generic except Exception, silently pins best_attempt_idx = 0, and the run continues as if nothing happened.

The same bare-except Exception pattern exists at reviewer.py:342-344 (Preselector) and reviewer.py:430-432 (Reviewer n-sample loop); the chooser is the most damaging of the three because its result gates the final submission choice.

Steps/commands/code to Reproduce

python
import sys
sys.path.insert(0, ".")

from sweagent.agent.models import GenericAPIModelConfig, AbstractModel
from sweagent.agent.reviewer import Chooser, ChooserConfig, ReviewSubmission
from sweagent.exceptions import TotalCostLimitExceededError
from sweagent.utils.log import get_logger


class RaisingModel(AbstractModel):
    def __init__(self, exc):
        self._exc = exc

    def query(self, *args, **kwargs):
        raise self._exc

    @property
    def stats(self):
        from sweagent.agent.models import InstanceStats
        return InstanceStats()


cfg = ChooserConfig(
    model=GenericAPIModelConfig(name="test-model"),
    system_template="system",
    instance_template="instance {{problem_statement}}",
    submission_template="submission {{submission}}",
)
chooser = object.__new__(Chooser)
chooser.config = cfg
chooser.logger = get_logger("chooser-poc")
chooser.model = RaisingModel(TotalCostLimitExceededError("boom"))

from sweagent.agent.models import InstanceStats
sub = ReviewSubmission(
    submission="solution text",
    trajectory=[],
    info={"exit_status": "submitted", "model_stats": {}},
    model_stats=InstanceStats(),
)
try:
    chooser.choose("problem", [sub, sub])
except Exception as e:
    print(f"{type(e).__name__}: {e}")

Expected: TotalCostLimitExceededError propagates (matching the agents.py:1180 intent; the caller at agents.py:369-370 already re-raises it, so propagation would be handled correctly by existing code). Actual: UnboundLocalError: local variable 'response' referenced before assignment is raised, and at the caller level it is caught by the generic handler at agents.py:371-373, which pins best_attempt_idx = 0 with only a critical log.

Verified on current main (3ea751c), Python 3.12, 2/2 for TotalCostLimitExceededError and InstanceCostLimitExceededError.

Error message/results

ERROR    chooser-poc:reviewer.py:366 Invalid chosen index: None, using first index
UnboundLocalError: local variable 'response' referenced before assignment

System Information

  • macOS 15, Python 3.12
  • SWE-agent 1.1.0 (main, 3ea751c)
  • Relevant config: config/benchmarks/250212_sweagent_heavy_sbl.yaml (chooser model o1, reasoning_effort high, per_instance_cost_limit 30, retry_loop cost_limit 6.0)

Checklist

  • I'm running with the latest docker container/on the latest development version (i.e., I ran git pull)
  • I have copied the full command/code that I ran (as text, not as screenshot!)
  • I have read the template and filled all required sections

Suggested fix

In Chooser.choose(), catch CostLimitExceededError before the bare except Exception and re-raise it (mirroring agents.py:1180-1186), and initialize response (e.g. to an empty string) so the fallback path cannot raise UnboundLocalError. The same split applies to the Preselector and Reviewer n-sample loops. Because agents.py:369-370 already re-raises TotalCostLimitExceededError, letting it propagate from the chooser requires no caller changes.