{"record":{"id":"c4ecc48ca1c0d721","repo":"NationalSecurityAgency/ghidra","slug":"transaction-already-started","errorCode":null,"errorMessage":"Transaction already started","messagePattern":"Transaction already started","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py","lineNumber":154,"sourceCode":"\n    def require_no_trace(self) -> None:\n        if self.trace != None:\n            raise RuntimeError(\"Trace already started\")\n\n    def reset_trace(self) -> None:\n        self.trace: Optional[Trace[Extra]] = None\n        util.set_convenience_variable('_ghidra_tracing', \"false\")\n        self.reset_tx()\n\n    def require_tx(self) -> Tuple[Trace, Transaction]:\n        trace = self.require_trace()\n        if self.tx is None:\n            raise RuntimeError(\"No transaction\")\n        return trace, self.tx\n\n    def require_no_tx(self) -> None:\n        if self.tx != None:\n            raise RuntimeError(\"Transaction already started\")\n\n    def reset_tx(self) -> None:\n        self.tx: Optional[Transaction] = None\n\n\nSTATE = State()\n\n\ndef ghidra_trace_connect(address: Optional[str] = None) -> None:\n    \"\"\"Connect Python to Ghidra for tracing.\n\n    Address must be of the form 'host:port'\n    \"\"\"\n\n    STATE.require_no_client()\n    if address is None:\n        raise RuntimeError(\n            \"'ghidra_trace_connect': missing required argument 'address'\")","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/NationalSecurityAgency/ghidra/blob/d5f144c24d6bc53c9cbf4448c6d11143e7696206/Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py#L136-L172","documentation":"Raised by State.require_no_tx() (commands.py:152-154) when STATE.tx is already a live Transaction. The agent forbids nesting transactions on a single trace: only one tx may be open at a time. Attempting to open a second tx (e.g. via ghidra_trace_tx_start) while the previous one is uncommitted triggers this.","triggerScenarios":"Calling ghidra_trace_tx_start() twice in a row without an intervening ghidra_trace_tx_commit() or ghidra_trace_tx_abort(); start_trace() being re-entered while a schema tx is still open; recursive helper that opens its own tx while the caller's tx is still open.","commonSituations":"Refactor that wrapped putmem in a helper which itself opens a tx, colliding with the caller's tx; exception in the middle of a tx block that left STATE.tx set and a retry tried to reopen; mismatched tx_start/tx_commit counts in a loop.","solutions":["Ensure each ghidra_trace_tx_start() has exactly one matching ghidra_trace_tx_commit() (or ghidra_trace_tx_abort()) before opening another.","If STATE.tx may be stale, call ghidra_trace_tx_abort() defensively once, then retry tx_start.","Factor helpers so they never open a transaction themselves — accept the tx from the caller."],"exampleFix":"// before\ndef write_block():\n    ghidra_trace_tx_start('block')   # collides if caller already opened one\n    ghidra_trace_putmem(a, l)\n    ghidra_trace_tx_commit()\n\n// after\ndef write_block():\n    ghidra_trace_putmem(a, l)   # caller owns the tx\n\n# caller:\nghidra_trace_tx_start('batch')\nwrite_block(); write_block()\nghidra_trace_tx_commit()","handlingStrategy":"validation","validationCode":"from ghidradbg.commands import STATE\nif getattr(STATE, 'tx', None) is not None:\n    raise RuntimeError('a transaction is already open; commit or abort first')","typeGuard":"def tx_is_closed() -> bool:\n    from ghidradbg.commands import STATE\n    return getattr(STATE, 'tx', None) is None","tryCatchPattern":"try:\n    ghidra_trace_tx_start('op')\nexcept RuntimeError as e:\n    if 'Transaction already started' in str(e):\n        ghidra_trace_tx_commit()   # or abort\n        ghidra_trace_tx_start('op')\n    else:\n        raise","preventionTips":["Keep helpers transaction-free; only the top-level caller opens tx.","Match every tx_start with exactly one commit/abort.","On exception mid-tx, abort before retrying."],"tags":["transaction","trace-lifecycle","debugger-agent","ghidra"],"backgroundTag":null,"analyzedSha":"d5f144c24d6bc53c9cbf4448c6d11143e7696206","analyzedAt":"2026-08-14T01:00:57.564Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}