Skip to content

fix(sdk): avoid ERROR-level logs for expected file-not-found on read#923

Merged
ninan-nn merged 3 commits into
opensandbox-group:mainfrom
hittyt:fix/filesystem-not-found-log-noise
May 21, 2026
Merged

fix(sdk): avoid ERROR-level logs for expected file-not-found on read#923
ninan-nn merged 3 commits into
opensandbox-group:mainfrom
hittyt:fix/filesystem-not-found-log-noise

Conversation

@hittyt

@hittyt hittyt commented May 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

The Kotlin SDK's FilesystemAdapter read 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 404 with 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-created stdout file). Reported by a user whose service was producing large volumes of ERROR logs like:

ERROR - c.a.o.s.i.a.s.FilesystemAdapter - Failed to read file with encoding UTF-8: /home/admin/mcp/mcp-proxy.stdout
com.alibaba.opensandbox.sandbox.domain.exceptions.SandboxApiException: Failed to read file. Status code: 404,
  Body: {"code":"FILE_NOT_FOUND","message":"file not found ... no such file or directory"}
    at ...FilesystemAdapter.readFile(FilesystemAdapter.kt:99)

Note the logger is FilesystemAdapter itself — 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.

  • Add SandboxError.FILE_NOT_FOUND constant.
  • Add Throwable.isFileNotFound() extension (statusCode == 404 or error.code == FILE_NOT_FOUND) so callers can branch on the not-found case cleanly too.
  • Route read-failure logging through a new 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

  • Unit tests
  • Integration tests
  • e2e / manual verification

Added FilesystemAdapterTest (5 cases, all passing via ./gradlew :sandbox:test):

  • readFile surfaces FILE_NOT_FOUND + statusCode 404 on a 404 response (so callers can distinguish it)
  • readFile returns content on success (regression guard)
  • isFileNotFound() true for FILE_NOT_FOUND, false for other API errors, false for non-sandbox exceptions

Breaking Changes

  • None

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

  • Linked Issue or clearly described motivation
  • Added/updated docs (if needed)
  • Added/updated tests (if needed)
  • Security impact considered
  • Backward compatibility considered

🤖 Generated with Claude Code

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>
@hittyt hittyt added bug Something isn't working sdks labels May 20, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

hittyt and others added 2 commits May 20, 2026 21:21
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>
@hittyt

hittyt commented May 20, 2026

Copy link
Copy Markdown
Collaborator Author

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 UNEXPECTED_RESPONSE, and silently downgrading those to DEBUG would hide real endpoint/routing/config regressions. I removed the || statusCode == 404 fallback so isFileNotFound() now classifies as not-found only when the server returns the explicit FILE_NOT_FOUND code (which execd does for missing files). Anything else stays loud at ERROR.

Added a regression test (isFileNotFound is false for a 404 without an explicit FILE_NOT_FOUND code) to lock this in.

@Pangjiping Pangjiping added sdk/java and removed sdks labels May 20, 2026
@ninan-nn ninan-nn merged commit f7dfbe8 into opensandbox-group:main May 21, 2026
23 checks passed

@ninan-nn ninan-nn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working sdk/java

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants