[GH-ISSUE #8223] [Design] Make lost Ghidra session edits visible after cancellation #6496

Open
opened 2026-11-01 21:11:40 +00:00 by bsakaguchi · 3 comments
Owner

Originally created by @morluto on GitHub (Oct 30, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/8223

Before submitting

  • I searched open and closed issues and pull requests for a related report. #8165 concerns durable Ghidra checkpoints, #6405 annotation export/replay, and #4227 broader lifecycle contracts.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Ghidra session lifecycle and MCP recovery.

Observed behavior

After a client cancels a Ghidra decompile, the edited ephemeral Program can be discarded while binary_session still returns the same summary as before cancellation. Its analysis_run contains a timestamped historical process-lineage snapshot; it is not itself a claim of a live process. The generic Ghidra limitations do explain that cancellation discards session annotations, but the status does not identify whether that loss occurred for this session.

Reproduction

On a self-built arm64 Mach-O dylib with a fast function and a deliberately large slow function, open real stdio MCP with Ghidra 12.1.4. Rename the fast function and add an entry comment with annotate_native_function, confirm the name and comment in its live pseudocode, then capture binary_session. Start the slow decompile and abort its client request after 300 ms. Wait until the previously recorded launcher and two recorded descendant PIDs are absent, then request binary_session again before another provider operation. Finally query the fast function and request status again.

In one settled run, the three recorded PIDs were absent 600 ms after the SDK abort. The before/after status summaries were exactly equal: the target stayed open, cleanup was not pending, analysis_activity was not_observed, and the same timestamped process-lineage snapshot was retained. A filtered Evidence summary still found the completed annotation record. The subsequent live pseudocode showed the original name without the comment; the following status had a newly observed Ghidra process snapshot but the same outer analysis run ID. The target SHA-256 was unchanged. This checks only the PIDs in that snapshot, not a complete process-family cleanup proof.

Expected behavior

Consider exposing a bounded session-state or provider-generation transition indicating that an edited Ghidra Program was discarded, with practical recovery guidance and a clear distinction between historical Evidence and current database state. Keep timestamped lineage as historical evidence and do not infer live ownership from a PID. Any state notice should preserve uncertainty where the cause or edit state is unknown.

Impact and scope

An agent recovering after cancellation can see a still-open target and historical annotation Evidence, but must infer from generic limitations and another live query that the edits were lost. This is a scripted macOS arm64 example, not an autonomous-agent failure rate, a data-corruption claim, or proof about abrupt crash/finalization paths. No target execution or executable mutation occurred.

REA version

53a4ab78451a0654e9c3beb3d43a5e371a288fcc; Ghidra 12.1.4, JDK 21, MCP TypeScript SDK 2.3.1.

Runtime and platform

macOS arm64, Node 24.22.1.

Interface

Real stdio MCP: annotate_native_function, binary_session, procedure_pseudo_code, and get_evidence_bundle.

Earliest failure stage

Recovery/status projection after client-side request cancellation; the status retains historical process observations but has no observed edit-loss transition.

Relevant output or evidence

  • SDK request returned an abort error after approximately 302 ms.
  • The captured launcher and two captured descendant PIDs were absent by 600 ms of polling.
  • binary_session before and after was identical; the later provider request returned the original function name and no comment.
  • Existing annotation Evidence remained discoverable, as historical data.

Typed outcome or recovery expected

Distinguish active target binding, provider Program lifetime, historical process snapshots and retained Evidence. Verify client cancellation, provider timeout that preserves the session, normal close, repeated opens, failure during finalization and cleanup retry. No new persistence or replay authority is implied.

Analysis or suspected cause

The session's outer run identity persists across provider restarts, and its process-lineage snapshots are observations rather than a current Program generation. This is source-informed interpretation of the observed output, not a complete root-cause diagnosis.

Originally created by @morluto on GitHub (Oct 30, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/8223 ### Before submitting - [x] I searched open and closed issues and pull requests for a related report. #8165 concerns durable Ghidra checkpoints, #6405 annotation export/replay, and #4227 broader lifecycle contracts. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Ghidra session lifecycle and MCP recovery. ### Observed behavior After a client cancels a Ghidra decompile, the edited ephemeral Program can be discarded while `binary_session` still returns the same summary as before cancellation. Its `analysis_run` contains a timestamped *historical* process-lineage snapshot; it is not itself a claim of a live process. The generic Ghidra limitations do explain that cancellation discards session annotations, but the status does not identify whether that loss occurred for this session. ### Reproduction On a self-built arm64 Mach-O dylib with a fast function and a deliberately large slow function, open real stdio MCP with Ghidra 12.1.4. Rename the fast function and add an entry comment with `annotate_native_function`, confirm the name and comment in its live pseudocode, then capture `binary_session`. Start the slow decompile and abort its client request after 300 ms. Wait until the previously recorded launcher and two recorded descendant PIDs are absent, then request `binary_session` again before another provider operation. Finally query the fast function and request status again. In one settled run, the three recorded PIDs were absent 600 ms after the SDK abort. The before/after status summaries were exactly equal: the target stayed open, cleanup was not pending, `analysis_activity` was `not_observed`, and the same timestamped process-lineage snapshot was retained. A filtered Evidence summary still found the completed annotation record. The subsequent live pseudocode showed the original name without the comment; the following status had a newly observed Ghidra process snapshot but the same outer analysis run ID. The target SHA-256 was unchanged. This checks only the PIDs in that snapshot, not a complete process-family cleanup proof. ### Expected behavior Consider exposing a bounded session-state or provider-generation transition indicating that an edited Ghidra Program was discarded, with practical recovery guidance and a clear distinction between historical Evidence and current database state. Keep timestamped lineage as historical evidence and do not infer live ownership from a PID. Any state notice should preserve uncertainty where the cause or edit state is unknown. ### Impact and scope An agent recovering after cancellation can see a still-open target and historical annotation Evidence, but must infer from generic limitations and another live query that the edits were lost. This is a scripted macOS arm64 example, not an autonomous-agent failure rate, a data-corruption claim, or proof about abrupt crash/finalization paths. No target execution or executable mutation occurred. ### REA version `53a4ab78451a0654e9c3beb3d43a5e371a288fcc`; Ghidra 12.1.4, JDK 21, MCP TypeScript SDK 2.3.1. ### Runtime and platform macOS arm64, Node 24.22.1. ### Interface Real stdio MCP: `annotate_native_function`, `binary_session`, `procedure_pseudo_code`, and `get_evidence_bundle`. ### Earliest failure stage Recovery/status projection after client-side request cancellation; the status retains historical process observations but has no observed edit-loss transition. ### Relevant output or evidence - SDK request returned an abort error after approximately 302 ms. - The captured launcher and two captured descendant PIDs were absent by 600 ms of polling. - `binary_session` before and after was identical; the later provider request returned the original function name and no comment. - Existing annotation Evidence remained discoverable, as historical data. ### Typed outcome or recovery expected Distinguish active target binding, provider Program lifetime, historical process snapshots and retained Evidence. Verify client cancellation, provider timeout that preserves the session, normal close, repeated opens, failure during finalization and cleanup retry. No new persistence or replay authority is implied. ### Analysis or suspected cause The session's outer run identity persists across provider restarts, and its process-lineage snapshots are observations rather than a current Program generation. This is source-informed interpretation of the observed output, not a complete root-cause diagnosis.
Author
Owner

@morluto commented on GitHub (Oct 30, 2026):

A separate real MCP control from the same investigation distinguishes a provider deadline from client cancellation. On an owned 2,000-branch fixture, I set the Ghidra per-function deadline to one second, renamed a fast function and added a comment, then decompiled the slow function. REA returned typed provider_timeout with guidance that only the requested decompilation stopped. The fast function still showed its name/comment; after raising ghidra.decompile_timeout_seconds to 60 at runtime, the slow function decompiled and the annotation remained. The target digest was unchanged. I did not capture binary_session in this control, so it does not establish a status transition; it shows why a future edit-loss notice must distinguish these two outcomes.

<!-- gh-comment-id:6534468214 --> @morluto commented on GitHub (Oct 30, 2026): A separate real MCP control from the same investigation distinguishes a provider deadline from client cancellation. On an owned 2,000-branch fixture, I set the Ghidra per-function deadline to one second, renamed a fast function and added a comment, then decompiled the slow function. REA returned typed `provider_timeout` with guidance that only the requested decompilation stopped. The fast function still showed its name/comment; after raising `ghidra.decompile_timeout_seconds` to 60 at runtime, the slow function decompiled and the annotation remained. The target digest was unchanged. I did not capture `binary_session` in this control, so it does not establish a status transition; it shows why a future edit-loss notice must distinguish these two outcomes.
Author
Owner

@morluto commented on GitHub (Oct 30, 2026):

I repeated the real Ghidra 12.1.4 timeout control with binary_session captured at the previously unmeasured stages. On the owned 2,000-branch Mach-O, I renamed/commented the fast function, captured status, hit a real one-second provider_timeout on the slow function, and captured status again. The post-timeout fast pseudocode retained the annotation. After setting the runtime limit to 60 seconds, slow decompilation succeeded, the fast annotation remained, and a third status was captured.

All three status results were exactly equal as parsed JSON in this run: target open, cleanup_pending: false, analysis_activity: not_observed, and the same timestamped historical process snapshot and outer run ID. The typed timeout response itself said only the decompile stopped and the session remained usable. This is a useful contrast with the original controlled caller-cancellation reproduction, where a similarly unchanged status preceded loss of edits on the next live query. A snapshot is historical, not proof of current process liveness, and I did not poll the full process family in this control. The source target digest stayed unchanged. This does not establish that every timeout preserves every state or that all cancellation paths discard it.

<!-- gh-comment-id:6535042567 --> @morluto commented on GitHub (Oct 30, 2026): I repeated the real Ghidra 12.1.4 timeout control with `binary_session` captured at the previously unmeasured stages. On the owned 2,000-branch Mach-O, I renamed/commented the fast function, captured status, hit a real one-second `provider_timeout` on the slow function, and captured status again. The post-timeout fast pseudocode retained the annotation. After setting the runtime limit to 60 seconds, slow decompilation succeeded, the fast annotation remained, and a third status was captured. All three status results were exactly equal as parsed JSON in this run: target open, `cleanup_pending: false`, `analysis_activity: not_observed`, and the same timestamped historical process snapshot and outer run ID. The typed timeout response itself said only the decompile stopped and the session remained usable. This is a useful contrast with the original controlled caller-cancellation reproduction, where a similarly unchanged status preceded loss of edits on the next live query. A snapshot is historical, not proof of current process liveness, and I did not poll the full process family in this control. The source target digest stayed unchanged. This does not establish that every timeout preserves every state or that all cancellation paths discard it.
Author
Owner

@morluto commented on GitHub (Oct 31, 2026):

Looping in @gensokyo-dev @kaoru0822-kitauji @N0zoM1z0 for visibility on the Ghidra, Kuna, and REA tool-design investigation.

<!-- gh-comment-id:6538802298 --> @morluto commented on GitHub (Oct 31, 2026): Looping in @gensokyo-dev @kaoru0822-kitauji @N0zoM1z0 for visibility on the Ghidra, Kuna, and REA tool-design investigation.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#6496
No description provided.