NationalSecurityAgency/ghidra · error · RuntimeError
Transaction already started
Error message
Transaction already started
What it means
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.
Source
Thrown at Ghidra/Debug/Debugger-agent-dbgeng/src/main/py/src/ghidradbg/commands.py:154
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
- 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.
Example fix
// before
def write_block():
ghidra_trace_tx_start('block') # collides if caller already opened one
ghidra_trace_putmem(a, l)
ghidra_trace_tx_commit()
// after
def write_block():
ghidra_trace_putmem(a, l) # caller owns the tx
# caller:
ghidra_trace_tx_start('batch')
write_block(); write_block()
ghidra_trace_tx_commit() Defensive patterns
Strategy: validation
Validate before calling
from ghidradbg.commands import STATE
if getattr(STATE, 'tx', None) is not None:
raise RuntimeError('a transaction is already open; commit or abort first') Type guard
def tx_is_closed() -> bool:
from ghidradbg.commands import STATE
return getattr(STATE, 'tx', None) is None Try / catch
try:
ghidra_trace_tx_start('op')
except RuntimeError as e:
if 'Transaction already started' in str(e):
ghidra_trace_tx_commit() # or abort
ghidra_trace_tx_start('op')
else:
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- No transaction
- Trace already started
- No transaction
- 'ghidra_trace_connect': missing required argument 'address'
- Invalid argument: {key_list[0]}
AI-assisted analysis of NationalSecurityAgency/ghidra@d5f144c24d (2026-08-14).
Data as JSON: /api/errors/c4ecc48ca1c0d721.
Report an issue: GitHub.