Scope
- Commit:
1a88d503e83ceb6688d41be1e00495abfd17f054
- Package:
@cellfence/trace (0.4.0)
- Test environment: Windows, Node.js v24.12.0
Actual and expected behavior
When an application's beforeExit listener schedules additional asynchronous work, observations recorded by that work are missing from the final evidence. Node.js can continue running application shutdown work after trace has already flushed its evidence during beforeExit.
The expected result is that both before-shutdown and during-shutdown, recorded before the process exits, are retained in the final evidence.
Reproduction
This reproduction uses the actual Node.js event loop and shutdown events. Only evidence writes are mocked; no database communication takes place.
import fs from "node:fs";
import path from "node:path";
const root = process.cwd();
const writes = [];
fs.mkdirSync = () => undefined;
fs.writeFileSync = (filename, data) => {
writes.push({ filename: path.resolve(filename), data: String(data) });
};
process.env.CELLFENCE_TRACE_COMMIT_SHA = "0000000000000000000000000000000000000000";
process.env.CELLFENCE_TRACE_OUT = path.join(root, "mock-evidence.json");
const trace = await import("./packages/trace/dist/index.js");
trace.recordDatabaseAccess("before-shutdown");
let lateRecorded = false;
process.once("beforeExit", () => {
setImmediate(() => {
trace.recordDatabaseAccess("during-shutdown");
lateRecorded = true;
});
});
process.once("exit", () => {
console.log({
lateRecorded,
flushCount: writes.length,
selectors: writes.flatMap(w => JSON.parse(w.data).accesses.map(a => a.selector))
});
});
Observed result:
{"lateRecorded":true,"flushCount":1,"selectors":["before-shutdown"]}
Cause and fix requirements
registerFlushHooks registers both beforeExit and exit hooks, but flushEvidence sets flushed = true on the first write and always skips subsequent writes. New observations added after the first flush need to be included when the process exits.
Validation scope
Verified using the trace implementation compiled from the specified commit. Because the environment was read-only, file creation and evidence writes were replaced with in-memory mocks. After building the repository, save the reproduction code as repro.mjs in the repository root and run it with node repro.mjs. The full test suite was not run.
If you find this repository useful, please consider giving it a star.
Scope
1a88d503e83ceb6688d41be1e00495abfd17f054@cellfence/trace(0.4.0)Actual and expected behavior
When an application's
beforeExitlistener schedules additional asynchronous work, observations recorded by that work are missing from the final evidence. Node.js can continue running application shutdown work after trace has already flushed its evidence duringbeforeExit.The expected result is that both
before-shutdownandduring-shutdown, recorded before the process exits, are retained in the final evidence.Reproduction
This reproduction uses the actual Node.js event loop and shutdown events. Only evidence writes are mocked; no database communication takes place.
Observed result:
{"lateRecorded":true,"flushCount":1,"selectors":["before-shutdown"]}Cause and fix requirements
registerFlushHooks registers both
beforeExitandexithooks, but flushEvidence setsflushed = trueon the first write and always skips subsequent writes. New observations added after the first flush need to be included when the process exits.Validation scope
Verified using the trace implementation compiled from the specified commit. Because the environment was read-only, file creation and evidence writes were replaced with in-memory mocks. After building the repository, save the reproduction code as
repro.mjsin the repository root and run it withnode repro.mjs. The full test suite was not run.If you find this repository useful, please consider giving it a star.