()
| 295 | } |
| 296 | |
| 297 | async #readStdoutFromFile(): Promise<string> { |
| 298 | const maxBytes = getMaxOutputLength() |
| 299 | try { |
| 300 | const result = await readFileRange(this.path, 0, maxBytes) |
| 301 | if (!result) { |
| 302 | this.#outputFileRedundant = true |
| 303 | return '' |
| 304 | } |
| 305 | const { content, bytesRead, bytesTotal } = result |
| 306 | // If the file fits, it's fully captured inline and can be deleted. |
| 307 | // If not, return what we read — processToolResultBlock handles |
| 308 | // the <persisted-output> formatting and persistence downstream. |
| 309 | this.#outputFileSize = bytesTotal |
| 310 | this.#outputFileRedundant = bytesTotal <= bytesRead |
| 311 | return content |
| 312 | } catch (err) { |
| 313 | // Surface the error instead of silently returning empty. An ENOENT here |
| 314 | // means the output file was deleted while the command was running |
| 315 | // (historically: cross-session startup cleanup in the same project dir). |
| 316 | // Returning a diagnostic string keeps the tool_result non-empty, which |
| 317 | // avoids reminder-only-at-tail confusion downstream and tells the model |
| 318 | // (and us, via the transcript) what actually happened. |
| 319 | const code = |
| 320 | err instanceof Error && 'code' in err ? String(err.code) : 'unknown' |
| 321 | logForDebugging( |
| 322 | `TaskOutput.#readStdoutFromFile: failed to read ${this.path} (${code}): ${err}`, |
| 323 | ) |
| 324 | return `<bash output unavailable: output file ${this.path} could not be read (${code}). This usually means another Claude Code process in the same project deleted it during startup cleanup.>` |
| 325 | } |
| 326 | } |
| 327 | |
| 328 | /** Sync getter for ExecResult.stderr */ |
| 329 | getStderr(): string { |
no test coverage detected