NationalSecurityAgency/ghidra · error · RuntimeError
Transaction already started
Error message
Transaction already started
What it means
Raised by State.require_no_tx() (commands.py:149-151) when STATE.tx is already non-None. The x64dbg trace agent keeps a single global STATE singleton holding at most one open Transaction; require_no_tx() is a precondition guard invoked by commands that must begin a fresh transaction (e.g. trace-start paths). A second start while the first is uncommitted trips the guard.
Source
Thrown at Ghidra/Debug/Debugger-agent-x64dbg/src/main/py/src/ghidraxdbg/commands.py:151
def require_no_trace(self) -> None:
if self.trace != None:
raise RuntimeError("Trace already started")
def reset_trace(self) -> None:
self.trace: Optional[Trace[Extra]] = None
util.set_convenience_variable('_ghidra_tracing', "false")
self.reset_tx()
def require_tx(self) -> Tuple[Trace, Transaction]:
trace = self.require_trace()
if self.tx is None:
raise RuntimeError("No transaction")
return trace, self.tx
def require_no_tx(self) -> None:
if self.tx != None:
raise RuntimeError("Transaction already started")
def reset_tx(self) -> None:
self.tx: Optional[Transaction] = None
STATE = State()
def ghidra_trace_connect(address: Optional[str] = None) -> None:
"""Connect Python to Ghidra for tracing.
Address must be of the form 'host:port'
"""
STATE.require_no_client()
if address is None:
raise RuntimeError(
"'ghidra_trace_connect': missing required argument 'address'")View on GitHub (pinned to d5f144c24d)
Solutions
- Close/commit the existing transaction first (call ghidra_trace_tx_end, or whichever end/commit your command provides) before starting a new one.
- Call STATE.reset_tx() in a finally block so an aborted transaction never leaks into the next command.
- Guard before starting: check 'if STATE.tx is not None' and end it before opening another.
- Wrap each transactional command body in try/finally that ends the tx on every exit path.
Example fix
// before
ghidra_trace_tx_start()
# ... error here, tx never closed ...
ghidra_trace_tx_start() # -> Transaction already started
// after
if STATE.tx is not None:
ghidra_trace_tx_end()
ghidra_trace_tx_start() Defensive patterns
Strategy: validation
Validate before calling
def safe_tx_start():
if STATE.tx is not None:
raise RuntimeError(f"Refusing to start tx: one is open already ({STATE.tx})")
# ... open the transaction ... Type guard
def tx_is_open() -> bool:
return STATE.tx is not None Try / catch
try:
ghidra_trace_tx_start()
except RuntimeError as e:
if 'already started' in str(e):
ghidra_trace_tx_end()
ghidra_trace_tx_start()
else:
raise Prevention
- Always pair tx_start with tx_end inside a try/finally.
- Reset STATE.tx in an exception handler so aborted commands do not leak an open tx.
- Before starting, assert STATE.tx is None.
When it happens
Trigger: Calling a command that calls STATE.require_no_tx() while a previous transaction is still open: e.g. ghidra_trace_tx_start invoked twice without a matching ghidra_trace_tx_end, or ghidra_trace_start after a tx_start with no tx_end/commit. Any nested transaction attempt on the singleton STATE.
Common situations: A script that opens a tx, hits an error before reaching the end/commit, then retries the same command; forgetting to pair tx_start with tx_end; running two trace commands in sequence where the first left the tx open due to an exception that was swallowed.
Related errors
AI-assisted analysis of NationalSecurityAgency/ghidra@d5f144c24d (2026-08-14).
Data as JSON: /api/errors/9a2072244fd4884c.
Report an issue: GitHub.