{"record":{"id":"b69ef0a26544652c","repo":"Hmbown/CodeWhale","slug":"wait-for-the-pet-recorder-write-before-closing","errorCode":null,"errorMessage":"Wait for the pet recorder write before closing.","messagePattern":"Wait for the pet recorder write before closing\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/scripts/lib/pet-recorder.mjs","lineNumber":166,"sourceCode":"            if (reopened.dev !== nextIdentity.dev || reopened.ino !== nextIdentity.ino)\n              throw new Error('The live pet recording was replaced externally after rotation.');\n            expectedMtime = reopened.mtimeNs;\n            await syncDirectory(dirname(path));\n            report(`Archived pet recording: ${archive}`);\n          } finally {\n            await next?.close();\n            if (created && !installed) await unlink(temporary);\n          }\n          return;\n        } else {\n          await output.writeFile(row);\n          expectedMtime = (await output.stat({ bigint: true })).mtimeNs;\n        }\n        bytes += size; sequence++;\n      } finally { busy = false; }\n    },\n    async close() {\n      if (busy) throw new Error('Wait for the pet recorder write before closing.');\n      const current = output, heldLock = lock; output = undefined; lock = undefined;\n      try { if (current) { try { await current.sync(); } finally { await current.close(); } } }\n      finally { heldLock?.close(); }\n    },\n  };\n}\n","sourceCodeStart":148,"sourceCodeEnd":173,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/scripts/lib/pet-recorder.mjs#L148-L173","documentation":"close() refuses to run while an append is still in flight (busy flag set). This protects the final fsync/close sequence from interleaving with an active write that still needs the output handle and lock.","triggerScenarios":"Calling close() without awaiting a pending append() promise; a shutdown handler closing the recorder while a write/rotation is mid-flight; a timeout path calling close() concurrently with a slow rotation.","commonSituations":"SIGTERM handlers that close the recorder synchronously while a queued append is running; forgetting to await append in a batch loop; finally-block ordering that closes before draining writes.","solutions":["Track in-flight appends and await them (e.g. Promise.all of pending appends) before calling close().","Move close() after the producer loop fully drains.","Use an async mutex/queue so close() waits behind pending writes.","If close() is called by a signal handler, set a flag, drain writes, then close."],"exampleFix":"// before\nawait recorder.append(last);\nclose(); // sync, races in-flight writes\n// after\nawait recorder.append(last);\nawait recorder.close();","handlingStrategy":"try-catch","validationCode":"if (pendingAppends.size > 0) throw new Error('cannot close: appends still in flight');","typeGuard":null,"tryCatchPattern":"try {\n  await Promise.all([...pendingAppends]);\n  await recorder.close();\n} catch (e) {\n  if (e.message.includes('Wait for the pet recorder write')) {\n    await Promise.allSettled([...pendingAppends]);\n    await recorder.close();\n  } else throw e;\n}","preventionTips":["Track every in-flight append promise and drain before close","In signal handlers: set a stopping flag, drain, then close","Never call close() concurrently with append()","Wrap close in a queue so it serializes behind writes"],"tags":["async","lifecycle","shutdown"],"backgroundTag":"invalid-state-transition","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}