FoundationAgents/OpenManus · warning · ToolError

Session has not started.

Error message

Session has not started.

What it means

Persistent bash tool session guard: stop() raises ToolError if _started is False, i.e. start() never spawned the bash process. stop() is meant to terminate a live shell; calling it on a session that was constructed but not started (or already stopped and never restarted) is a usage error.

Source

Thrown at app/tool/bash.py:50

        if self._started:
            return

        self._process = await asyncio.create_subprocess_shell(
            self.command,
            preexec_fn=os.setsid,
            shell=True,
            bufsize=0,
            stdin=asyncio.subprocess.PIPE,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )

        self._started = True

    def stop(self):
        """Terminate the bash shell."""
        if not self._started:
            raise ToolError("Session has not started.")
        if self._process.returncode is not None:
            return
        self._process.terminate()

    async def run(self, command: str):
        """Execute a command in the bash shell."""
        if not self._started:
            raise ToolError("Session has not started.")
        if self._process.returncode is not None:
            return CLIResult(
                system="tool must be restarted",
                error=f"bash has exited with returncode {self._process.returncode}",
            )
        if self._timed_out:
            raise ToolError(
                f"timed out: bash has not returned in {self._timeout} seconds and must be restarted",
            )

View on GitHub (pinned to 52a13f2a57)

Solutions

  1. Only call stop() after a successful start(): set a flag or assign the session only after await start() completes.
  2. In cleanup code, guard with the same condition the class uses: 'if session._started: session.stop()' — better, wrap stop() in try/except ToolError and ignore the not-started case.
  3. Structure setup as 'session = None; try: session = _BashSession(); await session.start() ... finally: if session ... stop()' so an unstarted session is never stopped.

Example fix

# before
session = _BashSession()
try:
    await session.start()
    ...
finally:
    session.stop()  # ToolError if start() raised

# after
try:
    session = _BashSession()
    await session.start()
    ...
finally:
    if session is not None and session._started:
        session.stop()
Defensive patterns

Strategy: validation

Validate before calling

if session._started:
    session.stop()

Type guard

def is_stoppable(s: _BashSession) -> bool:
    return bool(s._started) and s._process is not None and s._process.returncode is None

Try / catch

try:
    session.stop()
except ToolError as e:
    if 'Session has not started.' not in str(e):
        raise  # ignore the not-started case during cleanup

Prevention

When it happens

Trigger: Constructing _BashSession and calling stop() without start(); calling stop() twice after an exception in start(); cleanup paths (finally blocks) that stop a session whose start() threw.

Common situations: try/finally cleanup where start() failed and finally still calls stop(); generic teardown code iterating over sessions that may be unstarted.

Related errors


AI-assisted analysis of FoundationAgents/OpenManus@52a13f2a57 (2026-08-15). Data as JSON: /api/errors/1fdb11c5a334ce13. Report an issue: GitHub.