NationalSecurityAgency/ghidra · error · ElasticException

Could not recover unique executable via id

Error message

Could not recover unique executable via id

What it means

Thrown in queryExecutableRecordById when the total hit count for a specific executable _id within an msearch sub-response is not exactly 1. Since _id is the Elasticsearch primary key, exactly one document must match; a count of 0 means the document was deleted, and a count greater than 1 indicates index-level corruption or replication inconsistency.

Source

Thrown at Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/elastic/ElasticDatabase.java:1136

			buffer.append(exeId);
			buffer.append("\" }}}}}\n");
			count += 1;
			if (!iter1.hasNext()) {
				break;
			}
		}
		JsonObject bulkobj = connection.executeBulk(path, buffer.toString());
		JsonArray responses = (JsonArray) bulkobj.get("responses");
		for (int i = 0; i < count; ++i) {
			JsonObject subquery = (JsonObject) responses.get(i);
			JsonElement hits = subquery.get("hits");
			if (ElasticConnection.isNull(hits)) {
				throw new ElasticException("Multi-search for exe records failed");
			}
			JsonObject totalRec = (JsonObject) ((JsonObject) hits).get("total");
			long total = totalRec.get("value").getAsLong();
			if (total != 1) {
				throw new ElasticException("Could not recover unique executable via id");
			}
		}
		for (int i = 0; i < count; ++i) {
			JsonObject subquery = (JsonObject) responses.get(i);
			JsonObject hits = (JsonObject) subquery.get("hits");
			JsonArray hitsArray = (JsonArray) hits.get("hits");
			hits = (JsonObject) hitsArray.get(0);
			ExecutableRecord newExe = makeExecutableRecord(manager, hits);
			RowKey rowKey = iter2.next();
			manager.cacheExecutableByRow(newExe, rowKey);
		}
	}

	/**
	 * Query for function documents based on their parent executable id.
	 * A "page" of results is selected by selecting a -start- document and a maximum number to return
	 * @param exeId is the executable id
	 * @param maxDocuments is the maximum number of functions to return

View on GitHub (pinned to d5f144c24d)

Solutions

  1. Avoid concurrent delete and query operations on the same repository; serialize these operations.
  2. Set number_of_replicas appropriately and wait for green cluster health before querying to minimize stale replica reads.
  3. Re-run the query after concurrent operations have settled.
  4. If persistent, investigate Elasticsearch replication and read-consistency settings (e.g., set ?preference=_primary).
Defensive patterns

Strategy: validation

Validate before calling

// Before querying executable records, verify the document still exists
JsonObject check = connection.executeStatement(ElasticConnection.GET,
    "executable/_doc/" + exeId, "{}");
if (check.get("found") != null && !check.get("found").getAsBoolean()) {
    // Skip this executable — it was deleted
    return;
}

Try / catch

try {
    database.query(query);
} catch (ElasticException e) {
    if (e.getMessage().contains("Could not recover unique executable via id")) {
        // Concurrent deletion likely; skip or retry after operations settle
        Msg.warn(this, "Executable record not found (possibly deleted concurrently): " + e.getMessage());
    }
    throw e;
}

Prevention

When it happens

Trigger: After the hits null-check passes for a sub-response, totalRec.get("value").getAsLong() returns a value other than 1. This happens when the executable document no longer exists (deleted between id collection and the msearch) or when replica shards return inconsistent results.

Common situations: Concurrent deletion of the executable record by another process; Elasticsearch replica inconsistency causing stale reads; race condition during concurrent ingest/delete; read from a recovering shard that has not yet indexed the document.

Related errors


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