666ghj/MiroFish · error · RuntimeError

模拟停止失败

Error message

模拟停止失败

What it means

Generic RuntimeError raised by stop_simulation when, after termination, the (possibly refreshed) run state ends as FAILED but state.error is None/empty. It is a fallback message; normally the specific failure text is carried instead (see the branch above it that prefers state.error).

Source

Thrown at backend/app/services/simulation_runner.py:1077

                            RunnerStatus.FAILED,
                            state.error,
                        )
                        raise RuntimeError(state.error) from error
                state.runner_status = RunnerStatus.STOPPED
                state.twitter_running = False
                state.reddit_running = False
                state.completed_at = datetime.now().isoformat()
                state.error = None
                cls._save_run_state(state)
                cls._sync_simulation_status(
                    simulation_id,
                    RunnerStatus.STOPPED,
                )
                cls._manual_stop_requests.discard(simulation_id)

        state = cls.get_run_state(simulation_id) or state
        if state.runner_status == RunnerStatus.FAILED:
            raise RuntimeError(state.error or "模拟停止失败")
        if state.runner_status != RunnerStatus.STOPPED:
            raise RuntimeError(
                f"模拟停止未达到终态: {simulation_id}, status={state.runner_status}"
            )

        logger.info(f"模拟已停止: {simulation_id}")
        return state

    @classmethod
    def _read_actions_from_file(
        cls,
        file_path: str,
        default_platform: Optional[str] = None,
        platform_filter: Optional[str] = None,
        agent_id: Optional[int] = None,
        round_num: Optional[int] = None
    ) -> List[AgentAction]:
        """

View on GitHub (pinned to b5b53acc57)

Solutions

  1. Search logs around the stop for the real cause (the monitor thread logged it before marking FAILED).
  2. Audit any code that assigns RunnerStatus.FAILED directly to also set state.error.
  3. Re-fetch the run state / simulation record for the persisted error message, which may not be in the in-memory copy.

Example fix

# before
state.runner_status = RunnerStatus.FAILED
cls._save_run_state(state)  # later stop_simulation raises bare 模拟停止失败

# after
state.runner_status = RunnerStatus.FAILED
state.error = f"monitor exit code {proc.returncode}"
cls._save_run_state(state)
Defensive patterns

Strategy: try-catch

Try / catch

try:
    SimulationRunner.stop_simulation(sim_id)
except RuntimeError as e:
    state = SimulationRunner.get_run_state(sim_id)
    if state and state.runner_status == RunnerStatus.FAILED:
        # real cause is in logs / persisted record, not the exception text
        handle_failed_run(sim_id)
    else:
        raise

Prevention

When it happens

Trigger: Stop completes, state.runner_status == RunnerStatus.FAILED, and state.error was cleared or never set — e.g. an external writer (monitor thread or crash handler) set FAILED without a message, or a race cleared state.error between the FAILED assignment and this read.

Common situations: Rare defensive path; usually appears after custom patches that set runner_status = FAILED directly, or after a restart-recovery routine that reconstructs state without the error field.

Related errors


AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14). Data as JSON: /api/errors/140d231b4f58f5c1. Report an issue: GitHub.