NationalSecurityAgency/ghidra · error · IllegalStateException

Ghidra python interpreter has already been cleaned up.

Error message

Ghidra python interpreter has already been cleaned up.

What it means

Thrown by GhidraJythonInterpreter.push() (interactive line execution) when the interpreter's cleanedUp flag is true. Once cleanup() runs, the underlying Jython/InteractiveInterpreter state is torn down and the instance is permanently unusable. The class deliberately removes any reset path, so the only valid action is to obtain a fresh interpreter via GhidraJythonInterpreter.get().

Source

Thrown at Ghidra/Extensions/Jython/src/main/java/ghidra/jython/GhidraJythonInterpreter.java:166

				systemState.path.append(Py.newStringOrUnicode(pyDevSrcDir.getAbsolutePath()));
			}
		}
	}

	/**
	 * Pushes (executes) a line of Python to the interpreter.
	 *
	 * @param line the line of Python to push to the interpreter
	 * @param script a PythonScript from which we load state (or null)
	 * @return true if more input is needed before execution can occur
	 * @throws PyException if an unhandled exception occurred while executing the line of python
	 * @throws IllegalStateException if this interpreter has been cleaned up.
	 */
	public synchronized boolean push(String line, JythonScript script)
			throws PyException, IllegalStateException {

		if (cleanedUp) {
			throw new IllegalStateException(
				"Ghidra python interpreter has already been cleaned up.");
		}

		initializePythonPath();
		injectScriptHierarchy(script);

		if (buffer.length() > 0) {
			buffer.append("\n");
		}
		buffer.append(line);
		Py.getThreadState().tracefunc = interruptTraceFunction;
		Py.getSystemState().stderr = getSystemState().stderr; // needed to properly display SyntaxError
		boolean more;
		try {
			more = runsource(buffer.toString(), "python");
			getSystemState().stderr.invoke("flush");
			if (!more) {
				resetbuffer();

View on GitHub (pinned to d5f144c24d)

Solutions

  1. Stop using the cleaned-up instance; call GhidraJythonInterpreter.get() to create a new interpreter and route subsequent push() calls to it.
  2. Track the interpreter lifecycle with the owning component (e.g. dispose()/cleanup()) and null out references so stale callers cannot reach a dead instance.
  3. If unsure whether the instance is live, guard with a state check or catch IllegalStateException and reinitialize before retrying once.

Example fix

// before
interpreter.cleanup();
// ... later, in another thread
interpreter.push(line, script); // throws IllegalStateException

// after
interpreter.cleanup();
interpreter = GhidraJythonInterpreter.get();
interpreter.push(line, script);
Defensive patterns

Strategy: validation

Validate before calling

// There is no public isCleanedUp(); track lifecycle yourself, or wrap.
public boolean isInterpreterUsable(GhidraJythonInterpreter i) {
    return i != null; // add your own 'alive' flag tracked alongside cleanup()
}

Type guard

null

Try / catch

try {
    interpreter.push(line, script);
} catch (IllegalStateException e) {
    if (e.getMessage().contains("already been cleaned up")) {
        interpreter = GhidraJythonInterpreter.get(); // recreate once
        interpreter.push(line, script);
    } else throw e;
}

Prevention

When it happens

Trigger: Calling interpreter.push(line, script) after interpreter.cleanup() has been invoked on the same instance, e.g. a Jython REPL component that was disposed but a queued or background task still submits a line; or a long-lived JythonScript holding a stale interpreter reference whose owning plugin/tool was closed.

Common situations: Closing the Jython interpreter plugin window or the CodeBrowser tool while a script thread is mid-execution; headless runs that reuse an interpreter across script invocations after teardown; UI actions dispatched after the interactive console provider was disposed.

Related errors


AI-assisted analysis of NationalSecurityAgency/ghidra@d5f144c24d (2026-08-14). Data as JSON: /api/errors/a35d50e3da4d42c0. Report an issue: GitHub.