{"record":{"id":"3493fa7d98c6277e","repo":"t8y2/dbx","slug":"agent-exited-before-method","errorCode":null,"errorMessage":"agent exited before {method}","messagePattern":"agent exited before (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"agents/drivers/argo-go/bench/agent_compare.py","lineNumber":80,"sourceCode":"            with self.request_lock:\n                response_queue = self.pending.get(response_id)\n            if response_queue is not None:\n                response_queue.put(response)\n        self.exited.set()\n        self.ready.set()\n        with self.request_lock:\n            pending = list(self.pending.values())\n        for response_queue in pending:\n            response_queue.put(RuntimeError(self._failure(\"agent process exited\")))\n\n    def _drain_stderr(self) -> None:\n        assert self.process.stderr is not None\n        for line in self.process.stderr:\n            self.stderr_lines.append(line.rstrip())\n\n    def call(self, method: str, params: dict | None = None) -> object:\n        if self.exited.is_set():\n            raise RuntimeError(self._failure(f\"agent exited before {method}\"))\n        with self.request_lock:\n            self.request_id += 1\n            request_id = self.request_id\n            response_queue: queue.Queue = queue.Queue(maxsize=1)\n            self.pending[request_id] = response_queue\n        request = {\n            \"jsonrpc\": \"2.0\",\n            \"id\": request_id,\n            \"method\": method,\n            \"params\": params or {},\n        }\n        try:\n            assert self.process.stdin is not None\n            with self.write_lock:\n                self.process.stdin.write(json.dumps(request, separators=(\",\", \":\")) + \"\\n\")\n                self.process.stdin.flush()\n            response = response_queue.get(timeout=env_float(\"BENCH_RPC_TIMEOUT\", 180.0))\n        except queue.Empty as error:","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/t8y2/dbx/blob/c0390bff16418b651f4728520d99adf8ce48829a/agents/drivers/argo-go/bench/agent_compare.py#L62-L98","documentation":"AgentProcess.call raises this RuntimeError when an RPC method is invoked after the agent subprocess's stdout reader loop ended (the `exited` event is set), meaning the child process died or closed its stdout. The harness refuses to send a request to a dead process and fails fast, attaching captured stderr via _failure for diagnosis.","triggerScenarios":"Any call() to method X after the agent process exited: child crashed on startup or mid-run, closed stdout, was killed (OOM, signal), or a previous call timed out and the process was torn down while execute_workload/close/probe_paging/probe_failure_semantics/benchmark_workload/tdengine_websocket_live_compatibility kept issuing calls.","commonSituations":"Agent binary built from an incompatible version crashes when given a workload; OOM killer terminates a memory-heavy candidate during long benchmark runs; agent panics on a malformed SQL statement in a failure-semantics probe; PYTHONUNBUFFERED/stdio framing mismatch causes the reader loop to hit EOF early.","solutions":["Inspect process.stderr_lines (included in the failure message) for the agent's crash output and fix the underlying crash","Re-check the agent command/artifact path in BENCH_CANDIDATES and rebuild the agent so it starts cleanly","Catch RuntimeError in the driver, call close(), and restart AgentProcess for the candidate before retrying the workload","Raise BENCH_RPC_TIMEOUT or reduce workload size if the agent is being killed by OOM during runs","Verify the agent still emits a ready line and keeps stdout open for the whole session (protocol regression check)"],"exampleFix":"// before\nrows = process.call(\"execute_query\", params)\n// after\ntry:\n    rows = process.call(\"execute_query\", params)\nexcept RuntimeError as e:\n    if \"agent exited before\" in str(e):\n        process = AgentProcess(candidate)  # restart crashed agent\n        rows = process.call(\"execute_query\", params)\n    else:\n        raise","handlingStrategy":"try-catch","validationCode":"if process.exited.is_set():\n    process = AgentProcess(process.candidate)  # restart before calling\nrows = process.call(\"execute_query\", params)","typeGuard":"def agent_alive(process) -> bool:\n    return process.process.poll() is None and not process.exited.is_set()","tryCatchPattern":"try:\n    result = process.call(method, params)\nexcept RuntimeError as e:\n    if \"agent exited before\" in str(e):\n        log(process.stderr_lines)\n        process = AgentProcess(process.candidate)\n        result = process.call(method, params)\n    else:\n        raise","preventionTips":["Check process.exited / poll() before each call in long benchmark loops","Capture and persist stderr_lines to diagnose crashes post-mortem","Set BENCH_RPC_TIMEOUT so hangs are converted to TimeoutError instead of a silently-dead agent","Monitor for OOM kills (candidate rss_kib) and cap workload sizes"],"tags":["subprocess","rpc","process-crash","ipc"],"backgroundTag":"subprocess-exited-unexpectedly","analyzedSha":"c0390bff16418b651f4728520d99adf8ce48829a","analyzedAt":"2026-09-05T23:05:10.900Z","contentChangedAt":"2026-09-05T23:05:10.900Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}