Skip to content

fix: async-task client crash, hidden-path resolution, and two unusable doc examples - #5706

Open
xianml wants to merge 4 commits into
bentoml:mainfrom
xianml:fix/task-client-and-path-resolution
Open

fix: async-task client crash, hidden-path resolution, and two unusable doc examples#5706
xianml wants to merge 4 commits into
bentoml:mainfrom
xianml:fix/task-client-and-path-resolution

Conversation

@xianml

@xianml xianml commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

What

Four correctness fixes, each found by exercising the SDK while writing an agent skill for authoring Bentos (#5705, which is now skill-only and does not depend on this PR). Two code fixes, each with a test that fails when the fix is reverted; two doc pages whose examples cannot work as written.

1. SyncHTTPClient.<api>.submit() crashed on every call

with bentoml.SyncHTTPClient("http://localhost:3000") as client:
    task = client.my_task.submit(text="hello")
# AttributeError: 'SyncHTTPClient' object has no attribute '_opened_files'

The finally block in SyncHTTPClient._submit still cleaned up the pre-ClientFileManager attribute. _opened_files moved to ClientFileManager and is referenced nowhere else in the file, so every submit() raised — the request was sent and the task queued, but the caller only ever saw the AttributeError. AsyncHTTPClient._submit and SyncHTTPClient._call were already converted; this brings the last one over.

The async-task endpoints had no test coverage anywhere in the suite, which is how this shipped. Adds tests/e2e/fixtures/tasks/ (a trivial @bentoml.task service, no model) and three tests covering submit/status/get/retry, submit with a file argument, and the async client — ~2.6 s for the file. Two of the three fail before the fix.

2. A hidden ancestor of the working directory made every relative path illegal

$ BENTOML_HOME=~/.bentoml bentoml serve my_bento:latest
ValueError: Accessing hidden files is not allowed:
  /home/me/.bentoml/bentos/my_bento/<ver>/src/requirements.txt

resolve_user_filepath walked every part of the resolved absolute path, so any hidden directory above the working directory poisoned it. Serving a built bento whose service.py calls Image().requirements_file("requirements.txt") therefore failed for any dotted BENTOML_HOME — and the loader swallows it as "not a valid bento tag", so the reported error is the unrelated module-import failure.

The path above cwd is not user input, and cwd containment is already enforced one check earlier, so the hidden-file rule now applies to the relative part only. Dotfiles inside the project, escaping cwd, absolute paths and /etc+/proc all stay rejected — pinned by 7 new tests, two of which fail before the change.

3. Task status values and the cancel/retry methods were wrong in the docs and in the OpenAPI schema

ResultStatus is pending | in_progress | completed | failed | canceled, but the docs told readers to write if status.value == 'success' / 'failure' — branches that can never be taken — and TaskStatusResponse advertised ["in_progress", "success", "failure", "cancelled"], so a client generated from the spec disagrees with the server. Verified against a running server: GET /<api>/status returns "completed".

The two mutating routes were also swapped in the docs: retry is POST, cancel is PUT. Cancel is additionally unsupported by the local development server ("task cancellation is not supported in local development server"), which the page now says.

4. Every "custom service start command" example fails at startup

@bentoml.service(cmd=["uvicorn", "myapp:app", "--host", "$BENTOML_HOST", "--port", "$PORT"])
class ExternalServer: ...
# KeyError: 'PORT'  ->  Application startup failed. Exiting.

cmd entries go through expand_envs, i.e. string.Template.substitute(os.environ), which raises on a missing key — and BentoML defines neither PORT nor BENTOML_HOST. All four snippets on the page used them. Reproduced on 1.4.39, then verified that a hard-coded port matching http.proxy_port proxies correctly (GET / → 200 through the proxy).

The snippets now hard-code a port matching proxy_port and set workers=1, with a note covering both rules — the command must listen on http.proxy_port, and a custom-command Service defaults to min(16, cpu_count/2) workers of which only the first starts the process.

Testing

  • tests/unit/_internal/utils/test_filesystem.py — 7 tests, 2 fail without fix 2.
  • tests/e2e/bento_new_sdk/test_tasks.py — 3 tests, 2 fail without fix 1.
  • tests/unit shows the same 23 pre-existing environment failures (missing protobuf/grpc extras locally) before and after these commits, with 9 added passes.

🤖 Generated with Claude Code

jianshen92 and others added 4 commits August 26, 2026 18:33
`SyncHTTPClient.<api>.submit()` raised `AttributeError: 'SyncHTTPClient' object has
no attribute '_opened_files'` on every call: the `finally` block still cleaned up
the pre-`ClientFileManager` attribute, so the request was sent and the task queued
but the caller only ever saw the crash. `AsyncHTTPClient._submit` and
`SyncHTTPClient._call` were already converted; this brings the last one over.

The task endpoints had no test coverage anywhere in the suite, which is how this
shipped. Adds a fixture with a trivial `@bentoml.task` service (no model, ~2.5 s
for the file) covering submit/status/get/retry, submit with a file argument, and
the async client. Two of the three fail before the fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`resolve_user_filepath` walked every part of the resolved absolute path, so a
hidden *ancestor* of the working directory made any relative reference illegal.
Serving a built bento whose `service.py` calls
`Image().requirements_file("requirements.txt")` therefore failed with
"Accessing hidden files is not allowed" whenever BENTOML_HOME was a dotted path:

    BENTOML_HOME=~/.bentoml bentoml serve my_bento:latest
    -> ValueError: .../.bentoml/bentos/my_bento/<ver>/src/requirements.txt

The path above cwd is not user input, and cwd containment is already enforced one
check earlier, so the hidden-file rule now applies to the relative part only.
Dotfiles inside the project, escaping cwd, absolute paths and /etc//proc stay
rejected — covered by the new tests, two of which fail before this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`ResultStatus` is pending/in_progress/completed/failed/canceled, but the docs told
readers to compare against 'success' and 'failure' — a branch that can never be
taken — and the generated OpenAPI schema advertised the same wrong enum, so
clients generated from the spec disagreed with the server. Verified against a
running server: `GET /<api>/status` returns "completed".

The two mutating routes were also swapped: retry is POST, cancel is PUT. Cancel is
additionally unsupported by the local development server, which is now stated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s undefined

Every snippet in "Custom service start command" passed `$PORT` (one also
`$BENTOML_HOST`). `cmd` entries go through `expand_envs`, which substitutes from
`os.environ` and raises on a missing key, and BentoML defines neither variable —
so all four examples fail at startup with `KeyError: 'PORT'` (reproduced on
1.4.39). The port also has to match `http.proxy_port`, since that is where the
proxy sends requests, and a custom-command Service defaults to `min(16, cpu/2)`
workers of which only the first starts the process.

Snippets now hard-code a port matching `proxy_port` and set `workers=1`, with a
note covering both rules and what happens if `$VAR` is not in the environment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@xianml
xianml requested a review from a team as a code owner August 26, 2026 10:34
@xianml
xianml requested review from parano and removed request for a team August 26, 2026 10:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants