fix: address remaining greptile 4/5 items — random guard, ENOENT comment, array snapshot

1. Math.random() === 0 guard: (0).toString(36).slice(2,6) returns ''
   producing a trailing-hyphen slug. Added || '0000' fallback.

2. Misleading ENOENT comment: said 'when blockSessionSave was set before
   writeFileWithinRoot' but that branch requires writtenEntry !== null
   (blockSessionSave was false). Real scenario: external file deletion
   between inline write and post-hook drain. Comment corrected.

3. Live-array drain: for...of on postHookActions is a live iterator —
   a self-scheduling action could loop infinitely. Snapshot the array
   with [...(event.postHookActions ?? [])] before draining.
This commit is contained in:
zeroaltitude 2026-03-08 18:16:03 -07:00
parent c6110a3b15
commit a0073bfb9b
No known key found for this signature in database
GPG Key ID: 77592FB1C703882E
2 changed files with 14 additions and 7 deletions

View File

@ -332,7 +332,7 @@ const saveSessionToMemory: HookHandler = async (event) => {
// one silently overwrites the earlier memory entry.
if (!slug) {
const timeSlug = now.toISOString().split("T")[1].split(".")[0].replace(/:/g, "");
const rand = Math.random().toString(36).slice(2, 6); // 4-char alphanumeric
const rand = Math.random().toString(36).slice(2, 6) || "0000"; // 4-char alphanumeric
slug = `${timeSlug.slice(0, 6)}-${rand}`;
log.debug("Using fallback timestamp slug", { slug });
}
@ -439,11 +439,12 @@ const saveSessionToMemory: HookHandler = async (event) => {
await fs.unlink(memoryFilePath);
log.debug("Session save retracted by post-hook (blockSessionSave)");
} catch (err) {
// ENOENT is expected when the inline write didn't happen
// (e.g. blockSessionSave was set before writeFileWithinRoot).
// Re-throw so triggerInternalHook logs it. Note: the error
// is caught per-action and does NOT propagate to the caller;
// the file may remain on disk if unlink fails (e.g. EACCES).
// ENOENT can occur if the file was externally deleted between
// the inline write and the post-hook drain — not a concern.
// Re-throw non-ENOENT errors (e.g. EACCES, EROFS) so
// triggerInternalHook logs them. Note: errors are caught
// per-action and do NOT propagate to the session caller;
// the file may remain on disk under adversarial FS conditions.
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
throw err;
}

View File

@ -292,7 +292,13 @@ export async function triggerInternalHook(event: InternalHookEvent): Promise<voi
// a chance to mutate event.context, eliminating FIFO ordering issues.
// Actions execute in push order; errors are caught per-action so one
// failure doesn't block others.
for (const action of event.postHookActions) {
//
// Snapshot the array before draining so that actions pushed *by* post-hook
// callbacks do not execute in this drain cycle. Without this, a self-
// scheduling action (one that pushes another action) could loop infinitely
// because Array's for...of iterator is live and re-reads length each step.
const pendingActions = [...(event.postHookActions ?? [])];
for (const action of pendingActions) {
try {
await action();
} catch (err) {