HKUDS/Vibe-Trading · error · StaleGoalError
goal status {goal.status.value!r} is not mutable
Error message
goal status {goal.status.value!r} is not mutable What it means
The goal exists but its status is not in _CURRENT_STATUSES, so it is immutable (e.g. already completed, abandoned, or archived). The store refuses to mutate terminal-state goals.
Source
Thrown at agent/src/goal/store.py:839
if updated is None:
raise RuntimeError("usage-updated goal could not be reloaded")
return updated
def _require_mutable_goal(
self,
session_id: str,
goal_id: str,
expected_goal_id: str,
) -> GoalRecord:
if expected_goal_id != goal_id:
raise StaleGoalError("expected_goal_id does not match target goal")
session_id = normalize_required_text(session_id, "session_id")
goal_id = normalize_required_text(goal_id, "goal_id")
goal = self.get_goal(goal_id)
if goal is None or goal.session_id != session_id:
raise StaleGoalError("goal is not available for this session")
if goal.status not in _CURRENT_STATUSES:
raise StaleGoalError(f"goal status {goal.status.value!r} is not mutable")
current = self.get_current_goal(session_id)
if current is None or current.goal_id != goal_id:
raise StaleGoalError("goal is not current for this session")
return goal
@staticmethod
def _verification_status(evidence: EvidenceInput) -> str:
"""Return whether evidence has a traceable local artifact/run source."""
if evidence.artifact_path:
try:
artifact = safe_document_path(evidence.artifact_path)
except ValueError:
artifact = None
if artifact and artifact.is_file():
if GoalStore._artifact_hash_matches(artifact, evidence.artifact_hash):
return "verified"
if evidence.run_id:
try:View on GitHub (pinned to 80ffdda44c)
Solutions
- Check goal.status first and branch: completed goals need a new goal, not edits
- If the transition itself failed halfway, inspect the goal state before retrying
- Serialize status transitions per session so only one writer completes/abandons
Example fix
// before
store.append_evidence(session_id, goal_id, expected_goal_id, ...)
// after
goal = store._require_mutable_goal # or: goal = store.get_goal(goal_id)
if goal is not None and goal.status not in {"active", "in_progress"}:
raise RuntimeError(f"goal {goal.goal_id} is terminal: {goal.status}")
store.append_evidence(session_id, goal_id, expected_goal_id, ...) Defensive patterns
Strategy: validation
Validate before calling
goal = store.get_goal(goal_id)
if goal is None or goal.status not in _CURRENT_STATUSES:
handle_terminal_goal(goal) Type guard
def is_mutable_goal(goal, current_statuses) -> bool:
return goal is not None and goal.status in current_statuses Try / catch
except StaleGoalError as e: if 'not mutable' in str(e): treat goal as terminal — start a new goal, do not retry
Prevention
- Check status before every mutation attempt
- Serialize completion with other writers
- Never retry blindly on StaleGoalError
When it happens
Trigger: Calling update_goal/append_evidence/update_status/account_usage on a goal whose status was already moved to a terminal value (completed, abandoned, etc.).
Common situations: Retrying an operation after the goal was completed by a concurrent writer; stale UI showing an active goal; replaying an old request.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- {field_name} cannot be empty
- at least one goal criterion is required
- expected_goal_id does not match target goal
- goal is not available for this session
- goal is not current for this session
AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28).
Data as JSON: /api/errors/ab71ee5cf0700253.
Report an issue: GitHub.