ext-rdump: in-process RDUMP memory dumps for reli#1
Merged
Conversation
ext-rdump exposes rdump_dump(string $path, bool $full = false), which writes the calling PHP process's own memory into reli's RDUMP v3 dump format from inside the process -- no ptrace, no process_vm_readv. It resolves executor_globals / compiler_globals / basic_globals by their real addresses (offsetof from a stable member, so the same code works for both NTS and ZTS on PHP 7.0+), reads /proc/self/maps for the VMA list, and copies the relevant regions straight out of its own address space. Captured by default: anonymous, [heap]/[stack], writable file-backed segments and opcache shared memory -- enough for reli to walk ZendMM chunks, huge allocations, VM stacks and compiler arenas offline. $full = true also embeds read-only file-backed segments for a fully self-contained dump. The output is byte-compatible with Reli\Inspector\MemoryDump\ MemoryDumpWriter and analysable with: php reli inspector:memory:dump:inspect <file> php reli inspector:memory:analyze <file> https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
Mirrors reli's supported target range (PHP 7.0 .. 8.5) across both thread-safety builds. A GitHub Actions matrix runs each combination inside the official php:<ver>-cli (NTS) and php:<ver>-zts (ZTS) images. Checkout happens on the host runner and the build runs via `docker run` with the tree mounted, so node20-based actions don't have to execute on the old glibc of the 7.0-7.4 images. ci/build-and-test.sh installs PHPIZE_DEPS (falling back to archive.debian.org for the archived Debian releases those images use), builds with phpize/configure/make, runs the phpt suite when available, and ends with a version-agnostic smoke gate that asserts rdump_dump() writes a valid RDUMP file. https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
The php:7.0-7.x images run on archived Debian releases (stretch et al.) whose archive signing keys have expired, so apt-get install aborted with "repository is not signed". Permit the insecure archive explicitly in the fallback path (AllowInsecureRepositories + --allow-unauthenticated) so PHPIZE_DEPS installs on those containers. https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
Older run-tests.php (PHP 7.0-7.x) does not auto-detect the PHP binary and aborts with "TEST_PHP_EXECUTABLE must be set", which made the phpt suite silently no-op on those versions. Set it explicitly and also fail the step on run-tests' own ERROR: lines so a non-running suite can't pass as green. https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
actions/checkout@v4 runs on the deprecated Node.js 20 runtime. v5 runs on Node.js 24, clearing the deprecation warning. https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
- composer.json: type "php-ext" with a php-ext block (extension-name rdump, NTS+ZTS, linux only) so the extension can be installed with PIE, e.g. `pie install reliforp/ext-rdump`. - package.xml: PEAR/PECL 2.0 package definition so it can be built and installed with `pecl install package.xml` (or published to the PECL channel and installed via `pecl install rdump`). https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
Benefit-focused overview: take a point-in-time dump of the current PHP process and analyse it with reli (call stack, per-type memory and references, leak / resource-leak / cycle / bottleneck detection). Covers install (source / PIE / PECL), the rdump_dump() API with trigger examples (incl. a memory_limit-exhaustion shutdown handler), analysing with reli, and limitations. https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
AddressSanitizer cannot be used here: ASan instruments the same process whose whole address space this extension dumps, so reading ASan's own shadow-memory regions (which appear in /proc/self/maps under ASan) aborts in the fwrite interceptor before leak detection runs. valgrind completes the full dump path instead. The new leak-check job dumps repeatedly under USE_ZEND_ALLOC=0 valgrind and gates purely on the leak summary's lost-block counts. valgrind's "Invalid read" / "uninitialised write" notes are inherent to dumping raw process memory (page tails past the brk, never-written bytes) and are not leaks, so they are ignored. https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
phpize/autoheader leave config.h.in~ and configure~ backups in the tree. https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
Lead with what users get (point-in-time self-snapshot analysed by reli: call stack, per-object memory and references, automatic leak / resource- leak / cycle / bottleneck detection) instead of the internal mechanism. https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
The PECL summary and composer description show up in listings, so lead
with the benefit ("capture a PHP process's memory and analyse it with
reli to find leaks, reference cycles and memory bottlenecks") instead of
jargon like "in-process self-memory dump in RDUMP format".
https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
"...and analyse it with reli" read as if the extension analyses too. Reword to "...for offline analysis with reli" so the split is clear: the extension captures, reli analyses. https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
A trailing noun list ("(leaks, cycles, bottlenecks)") is idiomatic but
flat. Reword to "reli then analyses it to find leaks, reference cycles
and memory bottlenecks" so there is a clear verb and reli is the subject
doing the finding.
https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
"for offline analysis with reli (finding leaks, reference cycles, memory bottlenecks, etc.)" attaches the verb to reli's analysis and the "etc." keeps the list open-ended, reading more naturally than the bare noun list. https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
"without ptrace" is insider framing that doesn't mean much to a general user; match the package.xml summary wording instead. https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
rdump_dump() allocates via libc malloc, not the memory_limit-governed Zend heap, so it runs fine straight after a memory_limit fatal without an emergency reserve -- verified by exhausting a 16M limit and dumping from the shutdown handler. Simplify the example and note that the reserve the sidecar client needs is for its PHP-level work, not for this extension. https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
Earlier text wrongly said the bailout unwinds the VM stack. It does not: a bailout only cuts the call chain and leaves EG(vm_stack_top) where the OOM happened. rdump_dump()'s body uses libc malloc, but *calling* it pushes a VM-stack frame; the VM stack grows one ~256 KB page at a time, so the call reuses the current page (no emalloc) unless the stack sits at a page boundary, when a fresh page must be allocated and can fail at the limit. Note the ~256 KB reserve as the hard guarantee, matching why reli-prof-sidecar-client's MemoryLimitHandler keeps one. https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
Free a ~256 KB reserve at the top of the handler so the dump call's VM-stack frame is guaranteed headroom even when the stack sits at a page boundary. Comment kept terse; the dump body itself stays off the memory_limit heap (libc malloc). https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
Measured the moving parts: error_get_last() allocates ~376 B (the message is refcount-shared, so its length doesn't matter), strpos() 0 B, and a VM stack page is exactly 256 KB (262144). A VM-stack page extension is all-or-nothing, so a small reserve can't guarantee it anyway; its real job is the handler's own small post-OOM bookkeeping, which a few KB covers comfortably. Use a 4 KB reserve and describe it accurately. https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
rdump.oom_dump=<path> makes the extension dump from the engine's error
callback when an "Allowed memory size exhausted" fatal fires. This
captures OOMs a register_shutdown_function cannot: when the fatal is a
VM-stack allocation (or the stack sits at a page boundary), pushing the
shutdown closure's own frame also fails and the handler never runs. The
C hook fires on the intact stack before any unwind, and rdump_dump()'s
libc-malloc path is unaffected by the exhausted limit.
zend_error_cb's signature is version-branched (7.0-7.1 / 7.2-7.4 / 8.0 /
8.1+), mirroring php-memory-profiler. Adds module globals + INI
(rdump.oom_dump, rdump.oom_dump_full), MINIT/MSHUTDOWN install/restore,
and a re-entrancy guard. CI now exercises the hook on every version by
OOM-ing via pure recursion and asserting a dump is written.
Verified: pure recursion at memory_limit=2M produces no shutdown-handler
invocation ("in Unknown on line 0") yet the hook writes a valid 12 MB
RDUMP that reli inspects cleanly; phpt still passes; valgrind still
reports zero leaks.
https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
Lets code enable/disable and re-point the memory_limit auto-dump at
runtime, overriding the rdump.oom_dump INI for the current request:
- non-empty path -> enable (so callers can use a per-request/per-pid
filename the static INI cannot express);
- "" -> force-disable even if the INI set a path;
- null -> clear the override, fall back to the INI.
The runtime path is libc strdup/free-owned and reset in RSHUTDOWN so a
setting never leaks into the next request. The zend_error_cb hook now
resolves runtime-over-INI. Verified all three behaviours via real OOMs
and confirmed zero valgrind leaks across set/overwrite/null/strdup
cycles; CI now also exercises the runtime path on every version.
README notes the no-steady-state-overhead property (no executor/allocator
hooks; only a zend_error_cb chain on the error path) and that it
co-exists with other zend_error_cb users like php-memory-profiler.
https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
rdump_zend_error_cb runs on every diagnostic routed through zend_error_cb (notices, warnings, deprecations, ...). Wrap the "this is an actionable E_ERROR" test in UNEXPECTED() and move the runtime-vs-INI path resolution inside it, so the common path is a single predicted-not-taken comparison plus the chained call to the original handler -- no field reads on the hot error path. Mirrors memprof's EXPECTED-bail should_autodump. https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
Replace the two-paragraph, bullet-style blurb with a terse factual description in the style typical of PECL packages (a few present-tense sentences, no marketing). https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
Frames reli analysis as an affordance rather than asserting it as an automatic step, keeping the capture (extension) / analysis (reli) split clear. https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
Drop the "no ptrace" comparison (insider framing) and the PHP version line (already in <dependencies>); keep just "Linux only" as the one constraint not encoded elsewhere. https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
Collapse the three-sentence blurb into one relative-clause sentence (extension writes the dump, which reli analyses) plus "Linux only", matching the tighter feel of the composer.json description. https://claude.ai/code/session_017XEGHnz2ZakVXTdgLrtC7s
- Create the dump 0600 with O_NOFOLLOW/O_CLOEXEC (and fchmod a pre-existing file) so a memory image is never left group/world-readable. - Expand %p (pid) and %t (unix time) in the OOM auto-dump path so multi-worker setups (FPM) write distinct files instead of racing on one path. - Document the mid-dump crash risk under concurrent munmap/mprotect. https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
ZEND_INI_GET_ADDR() isn't available on the older (7.x) Zend headers, so the custom OnUpdateRdumpBytes handler compiled to an implicit declaration and the .so failed to load with "undefined symbol: ZEND_INI_GET_ADDR". Store rdump.oom_dump_max_total as a plain string (OnUpdateString) and parse it with rdump_parse_bytes() at use time instead. It's only read on the cold OOM path, and OnUpdateString works on every supported version. https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
The analyze step piped only stdout and checked grep, so a fatal/exception (printed to stderr) showed up as a bare "did not produce a report". Capture stdout+stderr, check the exit code, and dump the full output on failure so the real cause is visible in the CI log. https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
- README: the signal-handler example needs pcntl_async_signals(true) or the handler never fires on modern PHP. - rdump_parse_bytes(): clamp to ZEND_LONG_MAX instead of letting the suffix multiply overflow long long (or exceed zend_long on a 32-bit build) and wrap to a bogus/negative budget. (The other two suggestions are already in: 0600 + symlink tests in tests/file_perms.phpt, and the reli dump:inspect/analyze integration in ci/reli-integration.sh.) https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
"Don't let it run away" was colloquial and vague; match the descriptive style of the other subsections. https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
reli's own getting-started leads with the Docker image (a one-liner sets up a `reli` shell function), which avoids local PHP 8.4 / FFI / composer setup. Point there and drop the `php ` prefix so the examples work with the wrapper or a source install alike. https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
Show the docker:print-wrapper eval line inline so analysis is copy-paste runnable, and point to reli's getting-started for persisting it across shells. https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
The wrapper bind-mounts only the current directory, so the examples used a cwd-relative dump path now, and a note explains that paths outside cwd (including --dependency-root) need RELI_DOCKER_EXTRA_ARGS='-v /path:/path'. https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
inspector:memory:analyze outputs the analyzed graph (JSON by default); the type breakdown, retention, leaks, cycles, and findings come from the report formatter (-f report). https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
reli's analysis flow writes the analyzed graph to .rmem (json isn't suited to analysis); .rmem then feeds rmem:viz / rmem:explore. https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
inspector:memory:report reads the already-analyzed .rmem (or sqlite), so the example now runs analyze once into .rmem and reports from it rather than re-running analysis on the raw dump. https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
Under ZTS (e.g. FrankenPHP) a concurrent thread can unmap a region mid-dump and crash the process. rdump.safe_read=1 copies regions through /proc/self/mem with pread, so a concurrent unmap yields zero-filled bytes instead of a SIGSEGV (it doesn't fix inconsistency, only the crash). Off by default (direct copy is faster and NTS can't hit the race); recommended under ZTS. Add a %i (thread id) path token so concurrent OOM dumps from threads sharing one PID don't collide on a single file. CI smoke now also runs with safe_read=1. https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
- Correct the rdump_write_file() comment (no longer "default on for ZTS") and the "recommended on under ZTS" wording. - tests/safe_read.phpt: a dump written with rdump.safe_read=1 (regions via /proc/self/mem) is still a well-formed RDUMP. - CI: assert %i expands into the OOM dump path (oom-<pid>-<tid>.rdump). https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
…lback - package.xml: ship all four phpt tests (was just dump.phpt) and README.md, so PECL installs don't drop the suite. - README: the signal example used an arrow fn (PHP 7.4+); use a plain closure so it runs on the stated 7.0 floor. - rdump_parse_bytes(): the comment claimed "garbage -> 0" but the parse is lenient (trailing junk ignored); describe the actual behaviour. - OOM path: if a %p/%i template is too long to expand, skip the dump instead of falling back to the literal "%p" path, which would make every worker/thread clobber one shared file. https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
Previously any pread <= 0 was zero-filled, so a signal interruption (EINTR) would blank readable bytes. Retry the same offset on EINTR; zero-fill only on a real fault (EFAULT) or EOF, matching how rdump_read_maps() handles read(). https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
- rdump_write_file(): fold a fclose() failure into rc, so a deferred write error (NFS / quota / delayed writeback) surfacing only at the final flush fails the dump instead of looking successful (and no OOM .done marker is written for it). - safe_read: on a pread fault, zero just one page and resume past it rather than blanking the rest of the 1 MiB block, so a single concurrently-unmapped page doesn't lose the readable pages after it. - oom_dump_max_total: lstat() instead of stat() when summing *.rdump, so a symlink in the dump directory can't skew the budget with its target's size. https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
- Securing perms: only fchmod a pre-existing file (a new one is already 0600), and abort the dump if fchmod fails, so secret memory is never written into a file left group/world-readable. - safe_read: retry the bounce buffer at smaller sizes, and if /proc/self/mem still can't be set up, fail loudly instead of silently falling back to the crash-prone direct copy the user opted out of. - README: state the supported target is 64-bit little-endian Linux. https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
- open the dump (and the .done marker) with O_NONBLOCK and require S_ISREG, so a FIFO can't hang the OOM death path and devices/sockets are rejected, not just symlinks (O_NOFOLLOW). - Compile-time #error guards for non-Linux, non-64-bit, and big-endian targets, matching the documented support surface instead of silently emitting a malformed/wrong-width dump. https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
- file_perms.phpt: a non-regular target (/dev/null) is refused. - CI: %% expands to a literal percent; rdump.oom_dump_min_interval suppresses a second OOM dump within the window (built-in server, two requests). https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
Use ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX / ZEND_ARG_TYPE_INFO (both present since 7.0) so reflection and IDEs see the real signatures: rdump_dump(string $path, bool $full = false): bool rdump_set_oom_dump(?string $path, bool $full = false): bool No stub/gen_stub, which would emit arginfo that won't compile on the 7.0 floor. https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
Re-introduce the build-aware default: ZTS builds (where a concurrent thread can unmap a region mid-dump) enable safe_read by default, NTS builds keep the faster direct copy. Still overridable via the INI on either build. https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
…example - rdump_write_file(): instead of truncating an existing file in place (which leaves the new secret dump readable through any read fd another process already holds, and fchmod can't revoke), lstat the path, refuse anything not a regular file, then unlink and create a fresh inode with O_EXCL|0600. A stale fd then keeps the old content; the new dump lands on a new inode. tests/overwrite_inode.phpt locks this in. - README: the first php.ini example now lists oom_dump_max_total, oom_dump_marker, and safe_read (commented), so the important knobs are visible up front instead of only later. https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
…dump - rdump_parse_bytes(): use strtod so "1.5G" means ~1.5 GiB instead of being misread as 1 byte (which silently disabled the budget). A stray decimal point from a locale mismatch is skipped when locating the suffix, so it still can't collapse to a tiny budget; clamp huge values to ZEND_LONG_MAX. - .done marker: create with O_CREAT|O_EXCL|O_NOFOLLOW (the stale one is unlinked before the dump) and give up if the path already exists, instead of O_TRUNC-ing it -- matching the dump body's caution against truncating a regular file/hardlink or following a symlink. https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX takes a class_name arg (6 params) on PHP 7.0/7.1 but 5 on 7.2+, so the 5-arg form broke the 7.0/7.1 build. Use it only on >= 7.2 and fall back to ZEND_BEGIN_ARG_INFO_EX (no return type) on older; ZEND_ARG_TYPE_INFO is 4-arg on every version, so the typed arguments still apply everywhere. https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
- rdump_parse_bytes(): replace strtod with a hand-rolled decimal parser so '.' is always the decimal point. Under a "," locale strtod would mis-read "1.5G"; now it's parsed the same everywhere. - README: the dump replaces an existing regular file with a fresh 0600 inode (not fchmod-in-place), and `$full` embeds every readable mapping (read-only file-backed segments too, not just code), so the dump is larger. https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
MINFO hardcoded "3" while the dump header uses the RDUMP_FORMAT_VERSION macro; stringize the macro (RDUMP_FORMAT_VERSION_STR) so bumping the version updates both in one place. https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
A PHP extension that writes a memory dump of the current process in reli's
RDUMP format, from inside the process, without
ptrace. The file is the sameformat reli produces externally, so it is analysed with the usual
reli inspector:memory:analyze/inspector:memory:reportflow.The motivation is production capture: reli can already snapshot a process from
outside via
process_vm_readv/ptrace, but that needs ptrace granted to theruntime. This dumps from within instead, which is often easier to ship.
Output parity with reli
Verified on the same process: the default dump is byte-format identical and
captures the same regions as reli's external dump (same maps/region counts and
size; reli reads the file directly).
$full = trueadditionally embedsread-only file-backed segments for host-independent analysis (broader than
reli's targeted
--include-binary, so larger but never short on segments).In-process dumping is also markedly faster than the external read (~3.2 GB/s
direct vs reli's ~0.5 GB/s per-byte in a local tmpfs benchmark), since it avoids
the cross-process copy and PHP/FFI overhead.
memory_limit auto-dump
Opt-in via php.ini, written straight from
zend_error_cbon anAllowed memory size … exhaustedfatal — catching even the OOMs aregister_shutdown_functioncannot. Guarded so it can't run away:rdump.oom_dump— path (supports%pPID,%ithread id,%ttime,%%).rdump.oom_dump_max— dumps per worker process (default 1; 0 = unlimited).rdump.oom_dump_min_interval— minimum seconds between dumps.rdump.oom_dump_max_total— byte budget across*.rdumpin the dir (K/M/G/T,e.g.
1.5G), counting the incoming dump's own size.rdump.oom_dump_marker— also drop a<path>.doneonce complete, so awatcher never ships a half-written file.
rdump_set_oom_dump(?string $path, bool $full = false): bool— per-requestruntime override.
Safety / security
0600withO_EXCL/O_NOFOLLOW/O_CLOEXEC; an existing regular file isunlinked and replaced by a fresh inode (a stale read fd can't see the new
dump), and non-regular targets (symlink/FIFO/device/dir) are refused.
rdump.safe_readreads regions through/proc/self/mem, so a region anotherthread unmaps mid-dump yields zero-filled bytes instead of a crash.
Default on under ZTS (e.g. FrankenPHP), off under NTS (no concurrency, and the
direct copy is faster).
fclosefailures (deferred NFS/quota errors) are surfaced as dump failures.Compatibility / tests
guards reject unsupported targets. Typed arginfo without a stub (7.0-safe).
.phpttests plus CI across the full PHP × NTS/ZTS matrix: smoke (incl.safe_read), the OOM hook (INI + runtime),
%p/%i/%%expansion, thecount/interval/byte-budget guards, the completion marker (incl. stale-marker
handling), a valgrind leak check, and an integration job that has reli
actually
inspect/analyzean ext-rdump dump.https://claude.ai/code/session_017skw8BsHkF8wur7Pf4G9W5
Generated by Claude Code