Skip to content

fix(frida): drain admitted operations and honor queued cancellation - #2305

Merged
morluto merged 24 commits into
morluto:mainfrom
rohitg00:fix/frida-operation-lifecycle
Oct 13, 2026
Merged

morluto merged 24 commits into
morluto:mainfrom
rohitg00:fix/frida-operation-lifecycle

Conversation

@rohitg00

@rohitg00 rohitg00 commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Make Frida shutdown drain admitted operations and settle script-load cancellation before a queued resume can run. Cancelled operations no longer dispatch native effects after waiting for the session lock. Failed cleanup remains owned and retryable.

Related: #2535 and #2544.

Problem and expected behavior

A cancelled queued load, resume or unload could still execute, and shutdown could finish while attach or discovery was acquiring resources. Cancellation during native script loading also allowed a waiting resume to observe the cancelled script before service-level rollback ran.

The manager now owns admission, queue reservation, final load outcome and cleanup. An earlier operation reserves its session position synchronously, so a later close cannot overtake it. Cancellation during creation or loading releases the new script before returning. A failed unload retains the script identity and prevents premature resume. Once a load commits successfully, later cancellation cannot reclassify it; explicit unload releases it.

Change and scope

Drain admitted work, share close attempts, forward request cancellation and preserve cleanup ownership. Remove the service's superseded post-success rollback. Keep existing FIFO message capture, earlier scripts and caller-owned persistent sessions intact.

The Apple archive regression fixture also has a bounded 90-second compiler timeout within a 120-second test budget, preserving its assertions when a cold Swift cache exceeds the previous 30-second budget. Landed upstream CI and dyld fixture corrections are integrated without changing their implementation.

Contract and boundary impact

  • Semantic owner: manager admission and per-session native dispatch; the service projects the provider's settled outcome into Evidence.
  • Contribution scope: ordinary lifecycle correction within existing instrumentation authority; a new maintainer scope decision is not applicable.
  • CLI/MCP: schemas unchanged. Internal port calls accept optional cancellation signals; public error tags and partial-observation contracts remain intact.
  • Evidence: source identity, hashes, observed messages, script/session identities and cleanup uncertainty remain retained.
  • Authority and cleanup: selected-target behavior is unchanged. Cancellation does not detach persistent sessions, unload earlier scripts or kill targets. Failed cleanup remains retryable.
  • Package, installation and generated metadata: unchanged; owning Frida documentation is updated.

Expected existing effects:

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.

Evidence and regression coverage

Real MCP registration, Client/InMemoryTransport, service and manager tests with controlled native bindings cover queued cancellation, attach/discovery shutdown, synchronous admission, reentrant shutdown, creation/loading cancellation, failed cleanup and explicit retry. Ordering regressions fail before their producing-boundary fixes. Retained Evidence is checked against what queued native resume actually observed.

Cancelled-load results retain script identity and messages with script_cleanup: "released" or "incomplete_or_unknown"; committed successful loads remain explicitly unloadable.

  • Observed, derived and inferred claims remain distinguishable.
  • Artifact identity, source provenance and failed attempts remain preserved.
  • Unsupported, incomplete and uncertain outcomes remain visible.

Validation performed

After integrating upstream fb28ec1, using Node 24.18.0/npm 11.16.0 on macOS arm64:

  • npm run test:focused -- tests/boundary/mcp/fridaLifecycleMcp.test.ts src/application/frida/FridaInstrumentationManager.test.ts src/application/frida/FridaInstrumentationService.test.ts tests/boundary/mcp/fridaMcp.test.ts tests/boundary/cli/fridaCli.test.ts tests/boundary/cli/dylibResolutionCli.test.ts: 82 tests passed across six files, including the upstream dyld correction.
  • npm run check:ci: all eight tasks and architecture guards passed.
  • npm run docs:check: passed, including both README consistency tests.
  • git diff --check: passed. The upstream merge required no conflict resolution or additional source changes.

Earlier native Frida verification passed at 5792141 in a disposable Linux arm64 Debian Bookworm container with Frida 17.23.2: local spawn, marker message observed and successful cleanup. That receipt does not establish native verification of the final revision; later cancellation paths are verified with controlled bindings. The Apple fixture previously reproduced its timeout with a fresh Swift cache and passed with another fresh cache after the budget correction.

Compatibility, safety, and release

No breaking schema, dependency, migration or additional authority. Fresh final-revision native Frida verification, remote devices, Android/iOS and other native hosts remain unverified locally. Hosted CI is separate from local checks.

The previous hosted shard 3 failure is the existing ProviderDeadline.remainingMs() contradiction (expected zero after timeout, observed one millisecond). It remains tracked in open PR #2720; this PR does not duplicate that contributor's fix.

Before merge

  • Concrete problem, focused outcome and related issues stated.
  • Scope and existing authority are resolved.
  • Observable ordering, cancellation, failure and retry behavior are covered.
  • Owning documentation and representative Evidence are described.
  • Final diff checked for unrelated changes, secrets and unsupported claims.
  • Required hosted CI and final-revision real-provider evidence are complete.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@CassieFox23

Copy link
Copy Markdown
Contributor

TL;DR: Fixed, with real Frida on Linux: a cancelled queued load, unload or resume is never sent to the target. Shutdown now waits for an attach that is still running, and a failed cleanup there gets another try. Not tested for real: remote discovery cleanup.

I ran base 0365454 and head 344fc71 with real Frida, 3 times per case. Each run had a fresh MCP server and a fresh target, a small C program of my own.

Results

The table counts how often the old bug showed up.

What goes wrong Base 0365454 Head 344fc71
A cancelled queued load still loads its script 3 of 3 0 of 3
A cancelled queued unload still unloads the script 3 of 3 0 of 3
A cancelled queued resume still starts the paused target 3 of 3 0 of 3
A cancelled running load leaves its new script alive 3 of 3 0 of 3
A cancelled running load loses the session or earlier scripts 0 of 3 0 of 3
Shutdown says done, then an attach lands 3 of 3 0 of 3
A failed cleanup during an attach is not retried 3 of 3 0 of 3
A failed cleanup of an open session is not retried 0 of 3 0 of 3
  • The last three rows ran in one process with real Frida. Two of them used a made-to-fail detach() (see the fold).
  • Over a real stdio server, base and head looked the same for shutdown. Nothing was attached when the server exited.
  • The PR's test file passes 8 of 8 on head with Node 22, 24 and 26. On base it fails 8 of 8 (Node 24).

Not run

  • Remote devices (only the PR's mock test covers them), and shutdown during a device or process listing.
  • A cancel inside a native resume or unload call. I tested that for a load only.
  • Other platforms and CPUs, full suites, npm run check, docs:check and verify:frida.

Thanks, rohitg00, for the fix and for tests that cancel through the real MCP client.

How the runs worked

The key repro for the first row is short. A script that loops for 2 seconds holds the session. I queue a second load and cancel it with the SDK abort signal. On base, the second script writes its marker in the target. On head, it never does. The first script keeps running on both trees.

For the shutdown rows I ran REA's server in one process. The SDK client used an in-memory link, and Frida was real. On base, close() returned about 2 ms after the request. The attach landed about 53 ms after the request, and base released it about 8 ms later. On head, close() returned only after the attach was released.

The shutdown row used natural timing. A rerun with a 1.5 second pause before the real attach gave the same result as the natural timing, on both trees.

The attach cleanup row used that pause and a first real detach() that throws. On base, the first close() returned before the attach, and the target stayed attached after a second close(). On head, the first close() failed, and the second close() detached it.

The open session row used the throwing detach() only. Both trees failed the first close() and detached on the second.

Commands

Every run used its own server and its own target. I checked the target's log for the script markers. I checked its memory map for the Frida agent.

# per tree, Node 24.18.0, HOME set to an empty folder
npm ci && npm run build && npm run test:prepare    # exit 0, 0, 0 in both trees

# the target: calls named_function every 100 ms
gcc -O1 -rdynamic -o target target.c               # exit 0

# 7 cases x 2 trees x 3 runs, then 2 more cases x 2 trees x 3 runs
node cases.mjs <case> <tree> <label> <run>         # 54 runs, all exit 0

# the PR's test file, one Node at a time
npm run test:focused -- tests/boundary/mcp/fridaLifecycleMcp.test.ts

The test file exited 0 on head with Node 22.23.1, 24.18.0 and 26.11.1. It exited 1 on base with Node 24.18.0.

Test setup
  • Box: Nobara 44 (Fedora 44 base), Linux 7.2.9
  • CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads
  • RAM: 32 GB DDR4-3600
  • Node: 22.23.1, 24.18.0, 26.11.1 (the Frida runs used 24.18.0)
  • Head / base: 344fc71 / 0365454 (the PR's base)
  • Other: the PR's parent commit is aeee507 (feat(sqlite): inspect offline snapshots with selective evidence #2271). Its Frida files match base, so the results hold for both.
  • Other: main 8fc2043 has not changed these files since base.
  • Other: Frida 17.23.2 (LGPL-2.0 WITH WxWindows-exception-3.1)
  • Other: MCP SDK client 2.3.1 (Apache-2.0)
  • Other: the box was shared with another test job during these runs.

🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊

@morluto

morluto commented Oct 13, 2026

Copy link
Copy Markdown
Owner

@greptileai review

@greptile-apps

greptile-apps Bot commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

This PR is set for a Plus review. TREX isn't supported with Plus or Apex reviews yet, so Greptile reviews this PR at Base while TREX is on.

@greptile-apps

greptile-apps Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[High impact] The reviewed changes appear safe to merge; no new blocking issue was found.

Summary

This PR makes the Frida manager own shutdown admission and script-load cancellation.

  • Frida shutdown waits for admitted work before cleaning up.
  • Cancelled script loads settle before later session work runs.
  • Failed script cleanup keeps its identity and blocks target resume.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Frida operation] --> B{Shutdown started?}
  B -->|Yes| C[Reject new work]
  B -->|No| D[Track operation and reserve session queue]
  D --> E{Cancelled before native call?}
  E -->|Yes| F[Return cancellation]
  E -->|No| G[Run native operation]
  G --> H{Script load cancelled?}
  H -->|No| I[Return settled result]
  H -->|Yes| J[Release only the new script]
  J --> K{Cleanup succeeded?}
  K -->|Yes| L[Return cancellation with observation]
  K -->|No| M[Retain script and block resume]
  N[Shutdown] --> O[Stop admitting work]
  O --> P[Wait for tracked operations]
  P --> Q[Close retained resources]
  Q --> R[Retain failures for retry]
Loading

Reviews (13) · Last reviewed commit: "chore: refresh frida lifecycle PR with m..." · Reviewed by Greptile

rohitg00 and others added 9 commits October 13, 2026 15:07
Source-reviewed changes. Tests, builds and check reruns were skipped at maintainer request.
Node timers can fire while performance.now() is still a fraction of a
millisecond short of the monotonic deadline. ProviderStartupDeadline then
reported interruption "timeout" and an aborted signal while remainingMs()
returned ceil(0.x) = 1, so polling loops guarded by remainingMs() > 0
(Hopper and Ghidra startup) could run past an elapsed deadline, and the
real-timer test "aborts a bounded wait when its real monotonic deadline
expires" failed intermittently in CI (expected 1 to be 0).

Once the deadline's own timer has classified a timeout, report zero
remaining time. A deterministic regression test fires the timer with the
monotonic clock 0.4 ms short of the deadline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@morluto morluto added bug Something isn't working performance Performance regressions, scaling limits, and bounded-resource work area: runtime Process capture and runtime observation labels Oct 13, 2026
@morluto morluto added area: server Server layer and tool wiring area: domain Domain logic, errors, and pure functions P2: medium Medium priority issue labels Oct 13, 2026
morluto and others added 6 commits October 13, 2026 22:56
* fix(comparison): preserve incomplete function call evidence

Keep missing, unknown, partial and unresolved call relationships unknown in function comparisons and reconstruction claims. Aggregate IDA caller and callee coverage without overwriting uncertainty.

Validate with 144 focused tests, typecheck/lint, docs checks and test discovery. Real providers were not verified; the full headless IDA session suite requires unavailable Windows native controls.

* fix(comparison): recheck historical calls-only changes

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
(cherry picked from commit 688defc)
…2895)

* fix: repair the static-check failures on the default branch

The default branch fails its own required `Static checks` job three ways, so
every pull request reports that job as failed regardless of its own change.

- `npm run format:check` reports `src/domain/functionComparison.test.ts` and
  `src/ida/IdaOperationRunner.ts`.
- `npm run lint` reports a duplicate `import { join } from "node:path"` in
  `tests/boundary/mcp/ghidraInventoryMcp.test.ts` (lines 4 and 17).
- `npm run typecheck` reports `format: "devin"` missing from the client format
  union in `src/application/SupportedClients.ts`, and an unchecked index in
  `tests/boundary/cli/clientProfileLifecycle.test.ts`, whose pair array is
  inferred as `string[][]`.

Apply the pinned formatter, drop the duplicate import, list the `"devin"` format
that its own definition already uses, and mark the pair array `as const`.

Validation: `npm run check:fast` and `npm run format:check` pass.

* fix(setup): keep Devin on the JSON configuration dialect

---------

Co-authored-by: 吴佳浩 <wujiahao@supremind.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
(cherry picked from commit df3d96d)
@morluto
morluto merged commit 4481f86 into morluto:main Oct 13, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: domain Domain logic, errors, and pure functions area: runtime Process capture and runtime observation area: server Server layer and tool wiring bug Something isn't working P2: medium Medium priority issue performance Performance regressions, scaling limits, and bounded-resource work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants