{"record":{"id":"7958b6002d28ae52","repo":"affaan-m/ECC","slug":"standard-input-remained-unavailable-after-maxret","errorCode":null,"errorMessage":"Standard input remained unavailable after ${maxRetryWaitMs}ms.","messagePattern":"Standard input remained unavailable after (.+?)ms\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/memory.js","lineNumber":175,"sourceCode":"    && retryOptions.maxRetryWaitMs >= 0\n    ? retryOptions.maxRetryWaitMs\n    : MAX_STDIN_RETRY_WAIT_MS;\n  const wait = typeof retryOptions.wait === 'function'\n    ? retryOptions.wait\n    : waitForStdinRetry;\n  const chunks = [];\n  let total = 0;\n  let remainingRetryWaitMs = maxRetryWaitMs;\n  while (total <= maxBytes) {\n    const buffer = Buffer.alloc(Math.min(64 * 1024, maxBytes + 1 - total));\n    let bytesRead;\n    try {\n      bytesRead = fs.readSync(0, buffer, 0, buffer.length, null);\n    } catch (error) {\n      const retryable = ['EAGAIN', 'EWOULDBLOCK', 'EINTR'].includes(error?.code);\n      if (!retryable) throw error;\n      if (remainingRetryWaitMs < retryDelayMs) {\n        throw new Error(\n          `Standard input remained unavailable after ${maxRetryWaitMs}ms.`\n        );\n      }\n      wait(retryDelayMs);\n      remainingRetryWaitMs -= retryDelayMs;\n      continue;\n    }\n    if (bytesRead === 0) break;\n    chunks.push(buffer.subarray(0, bytesRead));\n    total += bytesRead;\n  }\n  if (total > maxBytes) {\n    throw new Error(`memory body is too large (maximum ${maxBytes} bytes).`);\n  }\n  return decodeUtf8(Buffer.concat(chunks, total), 'memory body from standard input');\n}\n\nfunction readBody(options) {","sourceCodeStart":157,"sourceCodeEnd":193,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/memory.js#L157-L193","documentation":"readBoundedStdin reads stdin via synchronous fs.readSync in a loop. When readSync throws EAGAIN/EWOULDBLOCK/EINTR (stdin not ready, e.g. a non-blocking pipe with no data yet), it waits retryDelayMs and retries, burning a budget of maxRetryWaitMs (default MAX_STDIN_RETRY_WAIT_MS = 5000). If the budget is exhausted before stdin yields data, it throws. It is not thrown for a clean EOF (bytesRead===0 ends the loop normally) — only for stdin that stays unavailable.","triggerScenarios":"Invoking `memory save --stdin` with no piped input and stdin connected to a non-blocking fd that never produces data. Running under a harness that connects a TTY-like fd marked non-blocking. A slow upstream producer that takes >5s to start; or a pipe that was opened but never written to and never closed.","commonSituations":"Calling the CLI interactively with --stdin and forgetting to pipe/redirect. A parent process that spawns memory with --stdin but never writes or closes the pipe. CI where stdin is /dev/null but opened in non-blocking mode that keeps returning EAGAIN instead of EOF.","solutions":["Always pipe content: `echo \"$body\" | ecc memory save --title x --stdin`, or redirect a file: `ecc memory save --title x --stdin < body.md`.","Prefer `--body-file <path>` for file sources; it avoids the stdin readiness problem entirely.","Ensure the producer closes stdin so the loop sees EOF (bytesRead===0) rather than indefinite EAGAIN.","If you must use --stdin with a slow producer, raise the budget by patching MAX_STDIN_RETRY_WAIT_MS or feeding input faster."],"exampleFix":"# before — no data on stdin, hangs then throws\necc memory save --title \"notes\" --stdin\n\n# after — pipe the body so stdin is ready immediately\necho \"Release went out at 10:00.\" | ecc memory save --title \"notes\" --stdin\n\n# or use a file source instead\n ecc memory save --title \"notes\" --body-file ./notes.md","handlingStrategy":"retry","validationCode":"// Before using --stdin, ensure there is actually a producer on the other end.\nconst fs = require('fs');\nfunction stdinLooksReady() {\n  try { return fs.fstatSync(0).isFIFO() || !process.stdin.isTTY; }\n  catch { return false; }\n}\nif (options.stdin && !stdinLooksReady()) {\n  throw new Error('--stdin given but no piped input detected; pipe content or use --body-file.');\n}","typeGuard":null,"tryCatchPattern":"try { body = readBoundedStdin(MAX_BODY_BYTES); }\ncatch (err) {\n  if (/remained unavailable/.test(err.message)) {\n    // Fall back to a file source if one was provided, else surface clearly.\n    body = options.bodyFile ? readRegularTextFile(options.bodyFile) : null;\n    if (!body) throw err;\n  } else throw err;\n}","preventionTips":["Prefer --body-file for file sources; reserve --stdin for genuine pipes.","Always close stdin from the producer so the loop sees EOF (bytesRead===0).","In CI, redirect `</dev/null` explicitly if no input, or pipe content.","Document the 5s retry budget so callers know slow producers need pre-buffering."],"tags":["stdin","io","memory-vault","retry-budget","node"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}