fix(sdk): avoid ERROR-level logs for expected file-not-found on read#923
Conversation
FilesystemAdapter read operations (readFile/readByteArray/readStream) caught every failure and logged it at ERROR with a full stack trace before rethrowing. A missing file (server returns HTTP 404 with code FILE_NOT_FOUND) is an expected, business-level outcome rather than a fault, so this floods callers' error logs and monitoring with noise for a normal control-flow case (e.g. polling for a not-yet- created stdout file). Distinguish "file not found" from genuine failures and log it at DEBUG instead of ERROR. The exception is still propagated unchanged. - Add SandboxError.FILE_NOT_FOUND constant. - Add Throwable.isFileNotFound() extension (statusCode 404 / code FILE_NOT_FOUND) so callers can also branch on it cleanly. - Route read-failure logging through logReadFailure() which downgrades not-found to DEBUG. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0c0e5ecbfb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Collapse the expression-body function to a single line as required by the project's spotlessCheck (root ./gradlew spotlessCheck). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address review feedback: isFileNotFound() previously treated any HTTP 404 as not-found. A 404 whose body cannot be parsed is mapped to UNEXPECTED_RESPONSE and may signal a real endpoint/routing regression; downgrading those to DEBUG would hide genuine failures. Restrict detection to the explicit SandboxError.FILE_NOT_FOUND code (which the execd server returns for missing files) and add a regression test covering a bare 404 + UNEXPECTED_RESPONSE. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Thanks for the review — good catch, addressed in 29fab0b. You're right that a bare HTTP 404 is ambiguous. A 404 whose body can't be parsed is mapped to Added a regression test ( |
Summary
The Kotlin SDK's
FilesystemAdapterread operations (readFile/readByteArray/readStream) catch every failure and log it at ERROR with a full stack trace before rethrowing.A missing file is not a fault — the execd server returns HTTP
404with body{"code":"FILE_NOT_FOUND", ...}, which is an expected, business-level outcome. Logging it at ERROR floods callers' error logs and monitoring with stack-trace noise for a perfectly normal control-flow case (e.g. polling for a not-yet-createdstdoutfile). Reported by a user whose service was producing large volumes of ERROR logs like:Note the logger is
FilesystemAdapteritself — the SDK emits this, not the caller, so callers cannot control the level.This change distinguishes "file not found" from genuine failures and logs the former at DEBUG. The exception is still propagated to the caller unchanged, so error-handling behavior is unaffected.
SandboxError.FILE_NOT_FOUNDconstant.Throwable.isFileNotFound()extension (statusCode == 404orerror.code == FILE_NOT_FOUND) so callers can branch on the not-found case cleanly too.logReadFailure()helper that downgrades not-found to DEBUG and keeps ERROR for real failures.Scope is intentionally limited to the read paths where the noise was reported. The broader log-and-throw pattern across the adapter (write/delete/etc.) is left for a follow-up.
Testing
Added
FilesystemAdapterTest(5 cases, all passing via./gradlew :sandbox:test):readFilesurfacesFILE_NOT_FOUND+statusCode 404on a 404 response (so callers can distinguish it)readFilereturns content on success (regression guard)isFileNotFound()true forFILE_NOT_FOUND, false for other API errors, false for non-sandbox exceptionsBreaking Changes
Public exception types and propagation are unchanged; only the SDK's internal log level for the not-found case changes (ERROR → DEBUG). A new error-code constant and extension function are additive.
Checklist
🤖 Generated with Claude Code