OpenHands / OpenHands/software-agent-sdk

[Bug]: Deferred initialization failure leaves a partially initialized runtime

Open
#4,658 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

architecture bug priority:medium ready-for-dev
Dominant language
Python
Stars
1.1k
Forks
539
Avg merge
1d 19h
Merged PRs (30d)
137

Description

Bug Description

InitService returns to dormant after an initialization exception without unwinding resources or restoring the process/app state already mutated by that attempt.

Initialization progressively changes environment/configuration, telemetry, the module-level conversation-service singleton, entered conversation/bash services, app.state, and app.root_path in openhands-agent-server/openhands/agent_server/init_router.py:223-268. The exception path at :271-278 resets only _error and _state.

A retry can therefore inherit live services, credentials, telemetry, or configuration from the failed attempt. A later retry can also overwrite the only cleanup reference for a resource entered by the failed attempt.

Expected Behavior

Initialization should be transactional: dormant must imply that no staged runtime resources or configuration from a failed attempt remain live, and a retry should start cleanly.

Actual Behavior

An independent, self-contained reproducer is posted in the issue comments. Save
it as /tmp/repro_4658.py, then run:

uv run python /tmp/repro_4658.py

On SDK commit 6d3881035, it exercises real FastAPI, InitService, ConversationService, BashEventService, filesystem failure, and teardown; no service mocks and prints:

http_status=500
state_after_failure=dormant
env_after_failure=leaked-from-failed-init
conversation_singleton_replaced=True
bash_service_still_retained=True
telemetry_sink_published=True
Suggested Implementation

Build a private runtime locally using an AsyncExitStack (effective config, conversation service, bash service, telemetry and other resources). Publish it to app.state and swap any compatibility singleton only at one successful commit point. On failure, unwind the stack and restore staged environment/global values before returning to dormant.

The ordinary and deferred startup paths should ultimately resolve the same app-owned runtime, while preserving the current REST contract.

Acceptance Criteria
  • Failure injected after each resource-entry phase exits every entered resource exactly once.
  • Failed initialization leaves bootstrap authentication, app.state, root path, environment, telemetry, and service globals unchanged.
  • A retry after every injected failure succeeds.
  • Successful teardown preserves the existing service exit order.
  • InitRequest, InitStatus, HTTP statuses, headers, and OpenAPI remain unchanged.
Version

Current main at 6d3881035. Found during a read-only state/ownership audit; no repository files were changed and no tests were run.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with openhands-agent-server/openhands/agent_server/init_router.py:223-278 and run the comment-provided /tmp/repro_4658.py reproducer with uv. Trace each initialization resource and the existing teardown order, then add failure-injection coverage for the listed phases. Done means failed attempts leave state unchanged, retries succeed, teardown order remains intact, and the REST contract is unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
fastapi, python
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.