feat: add bash_events_retention_seconds for automatic bash event cleanup#3362
Conversation
…eanup Add a new Config field bash_events_retention_seconds (default None = keep forever) that triggers a background asyncio task running in the app lifespan to periodically delete bash event files older than the configured window. Changes: - config.py: add bash_events_retention_seconds: int | None field - bash_service.py: add delete_events_older_than(cutoff) for efficient age-based file deletion using the YYYYMMDDHHMMSS filename prefix for early-exit sorted scanning, and run_retention_cleanup_loop() as the cancellable background coroutine - api.py: start/cancel the retention task in api_lifespan using the existing suppress(CancelledError) pattern - test_bash_service.py: unit tests for delete_events_older_than (empty dir, removes old, keeps new, correct count) and an integration test for the loop using a short interval_seconds to avoid 60s wait Co-authored-by: openhands <openhands@all-hands.dev>
Python API breakage checks — ✅ PASSEDResult: ✅ PASSED |
REST API breakage checks (OpenAPI) — ✅ PASSEDResult: ✅ PASSED |
Coverage Report •
|
||||||||||||||||||||||||||||||
all-hands-bot
left a comment
There was a problem hiding this comment.
🟡 Taste Rating: Acceptable. One cleanup-path performance issue should be addressed before approval.
Risk: 🟡 Medium — optional config, but when enabled it can affect server responsiveness under exactly the large-directory condition this feature targets.
This review was generated by an AI agent (OpenHands) on behalf of the user.
Was this automated review useful? React with 👍 or 👎 to this review to help us measure review quality.
Workflow run: https://github.com/OpenHands/software-agent-sdk/actions/runs/26334611388
all-hands-bot
left a comment
There was a problem hiding this comment.
Clean, well-scoped feature. The filename-prefix early-exit scan is clever and the test coverage is solid. Two issues to address before merging:
- Task leak (🟠) —
retention_taskis created beforeasync with service:, so ifservice.__aenter__()raises the task is never cancelled. - Sleep-first pattern (🟡) — the cleanup loop skips an initial run on startup, leaving stale files sitting for up to
max(60, retention_seconds / 2)seconds after a restart.
This review was generated by an AI agent (OpenHands) on behalf of the user through OpenHands Automation.
all-hands-bot
left a comment
There was a problem hiding this comment.
⚠️ QA Report: PASS WITH ISSUES
Functional QA confirms the new bash-events retention setting works end-to-end through the real agent-server API; CI still had one failing image build job and several pending jobs when checked.
Does this PR achieve its stated goal?
Yes. The stated goal was to add OH_BASH_EVENTS_RETENTION_SECONDS / bash_events_retention_seconds so the agent server automatically purges old workspace/bash_events files without API changes. I ran the actual agent-server, executed real bash commands through POST /api/bash/execute_bash_command, and verified that main retained event files after 70s while this PR deleted them on the configured retention loop and continued accepting new bash commands afterward.
| Phase | Result |
|---|---|
| Environment Setup | ✅ make build completed and installed the uv workspace dependencies. |
| CI Status | Build & Push (java-arm64) was failing and some image/QA jobs were still pending at review time. |
| Functional Verification | ✅ Real server/API exercise confirmed old bash event files are purged when retention is configured. |
Functional Verification
Test 1: Baseline on main keeps bash events indefinitely
Step 1 — Establish baseline without the fix:
Checked out origin/main, started the real server with the future env var set, then executed a bash command through the public API:
OH_WORKSPACE_DIR=/tmp/oh-qa-main/workspace \
OH_BASH_EVENTS_DIR=/tmp/oh-qa-main/bash_events \
OH_BASH_EVENTS_RETENTION_SECONDS=1 \
uv run agent-server --host 127.0.0.1 --port 8123
curl -sS -X POST http://127.0.0.1:8123/api/bash/execute_bash_command \
-H 'Content-Type: application/json' \
-d '{"command":"printf baseline-retention-check","cwd":"/tmp/oh-qa-main/workspace","timeout":5}'Observed:
{"exit_code":0,"stdout":"baseline-retention-check","kind":"BashOutput"}
event_files_after_command=2
20260523140029_BashCommand_af7c2e0b86684937b827c1e0ba4c4d72
20260523140029_BashOutput_af7c2e0b86684937b827c1e0ba4c4d72_99398145964a44d7a9a4c62b393a631fAfter waiting 70 seconds, the same files were still present and discoverable via the unchanged search endpoint:
event_files_after_70s=2
GET /api/bash/bash_events/search?limit=10 -> 2 itemsThis confirms the previous behavior: polling/searching did not trigger cleanup, so bash event files remained on disk.
Step 2 — Apply the PR's changes:
Checked out feat/bash-events-retention at ba6d8be4d1128aaadb988e607d83fe89ea881d8c and started the server with the same retention env var. Startup logs included:
Bash events retention cleanup started (retention: 1s)
Step 3 — Re-run with the fix in place:
Executed the same API flow against the PR server:
curl -sS -X POST http://127.0.0.1:8123/api/bash/execute_bash_command \
-H 'Content-Type: application/json' \
-d '{"command":"printf pr-retention-check","cwd":"/tmp/oh-qa-pr/workspace","timeout":5}'Observed initial command success and event-file creation:
{"exit_code":0,"stdout":"pr-retention-check","kind":"BashOutput"}
event_files_after_command=2After waiting 70 seconds for the configured cleanup loop:
event_files_after_70s=0
GET /api/bash/bash_events/search?limit=10 -> {"items": [], "next_page_id": null}The server log showed the background cleanup actually deleted the files:
Deleted 2 bash event file(s) older than 20260523140306
This confirms the new retention configuration is honored by the running application and bounds on-disk bash event growth.
Test 2: Bash API still works after cleanup
After the retention loop removed the old files, I executed another real command through the same endpoint:
curl -sS -X POST http://127.0.0.1:8123/api/bash/execute_bash_command \
-H 'Content-Type: application/json' \
-d '{"command":"printf post-cleanup-command-ok","cwd":"/tmp/oh-qa-pr/workspace","timeout":5}'Observed:
{"exit_code":0,"stdout":"post-cleanup-command-ok","kind":"BashOutput"}
event_files_after_post_cleanup_command=2This shows cleanup did not break subsequent bash command execution or event persistence for new commands.
Issues Found
- 🟠 Issue: CI was not fully green at review time:
Build & Push (java-arm64)was failing, with several image-build jobs and this QA workflow still pending. Functional verification of the PR goal passed, but maintainers should check the CI failure before merge.
This review was created by an AI agent (OpenHands) on behalf of the user.
- Make delete_events_older_than a plain sync method; all its operations are blocking filesystem I/O with no awaits - In run_retention_cleanup_loop, dispatch blocking work to a thread via asyncio.to_thread so the event loop is never stalled during glob/sort/unlink of a large bash_events directory - Move retention task creation inside 'async with service:' so the task is never spawned if service __aenter__ fails, eliminating the orphaned-task edge case - Run cleanup immediately on loop entry (before the first sleep) so files accumulated across a server restart are purged right away - Update tests: delete_events_older_than unit tests are now plain sync functions; loop test still async via asyncio.to_thread path Co-authored-by: openhands <openhands@all-hands.dev>
all-hands-bot
left a comment
There was a problem hiding this comment.
Clean, well-scoped feature and all previously raised issues (task-leak, sleep-first ordering, event-loop blocking, spurious async) are correctly resolved in the current commit. Two small items worth addressing before merge:
- Config description omits the critical in-flight command warning (🟡) — operators configuring this via env var won't see the PR-body caveat.
- No error backoff in the cleanup loop (🟡) — a persistent filesystem error will spam the log at full interval frequency.
This review was generated by an AI agent (OpenHands) on behalf of the user through OpenHands Automation.
all-hands-bot
left a comment
There was a problem hiding this comment.
Found one config-wiring issue that needs attention before approval.
This review was generated by an AI agent (OpenHands) on behalf of the user.
Was this automated review useful? React with 👍 or 👎 to this review to help us measure review quality.
Workflow run: https://github.com/OpenHands/software-agent-sdk/actions/runs/26335525769
all-hands-bot
left a comment
There was a problem hiding this comment.
✅ QA Report: PASS
Verified the agent-server bash event retention feature end-to-end with real HTTP bash execution; retention now bounds workspace/bash_events/ when configured and default behavior remains unchanged.
Does this PR achieve its stated goal?
Yes. On main, starting the server with OH_BASH_EVENTS_RETENTION_SECONDS=1 did not remove aged bash events: after executing a bash command and restarting, GET /api/bash/bash_events/search still returned the command/output events and 2 files remained. On PR commit 77c0e267, the same user flow deleted the aged files on startup, and a live server also deleted newly-created bash events after the default cleanup interval. With the env var unset, events still persisted across restart, matching the documented default of retaining indefinitely.
| Phase | Result |
|---|---|
| Environment Setup | ✅ make build completed successfully |
| CI Status | 🟡 32 successful, 4 skipped, 1 pending (QA Changes by OpenHands) when checked |
| Functional Verification | ✅ Started real agent-server processes, made HTTP bash API calls, and verified on-disk side effects |
Functional Verification
Test 1: Baseline vs PR cleanup on server restart
Step 1 — Reproduce / establish baseline without the fix:
Checked out origin/main, started the server with retention configured, executed a real bash command through the HTTP API, waited past the 1-second window, and restarted the server with the same env var:
git checkout origin/main
OH_WORKSPACE_BASE=/tmp/oh-qa-base/workspace OH_BASH_EVENTS_DIR=/tmp/oh-qa-base/workspace/bash_events OH_BASH_EVENTS_RETENTION_SECONDS=1 uv run agent-server --host 127.0.0.1 --port 18101
POST /api/bash/execute_bash_command {"command": "printf 'qa-retention-baseline\n'", "timeout": 5}
GET /api/bash/bash_events/searchObserved after restart:
BASELINE_GIT_SHA 3d9fc105
BASELINE_RETENTION_ENV_SECONDS 1
BASELINE_SEARCH_COUNT_AFTER_RESTART 2
BASELINE_EVENT_KINDS_AFTER_RESTART ['BashCommand', 'BashOutput']
BASELINE_STDOUT_PRESENT True
BASELINE_FILES_AFTER_RESTART 2
This confirms the pre-PR behavior: even with the env var set, bash event history and files were retained.
Step 2 — Apply the PR's changes:
Checked out PR commit 77c0e26792a00c1276b0e21545adb934174e6193 and repeated the same server/API flow with OH_BASH_EVENTS_RETENTION_SECONDS=1.
Step 3 — Re-run with the fix in place:
Ran the same execute/search/restart flow on the PR branch:
PR_GIT_SHA 77c0e267
DELETE_STATUS 200
EXEC_STATUS 200
EXEC_STDOUT 'qa-retention-pr
'
SEARCH_COUNT_AFTER_EXEC 2
EVENT_KINDS_AFTER_EXEC ['BashCommand', 'BashOutput']
STDOUT_PRESENT_AFTER_EXEC True
FILES_AFTER_EXEC 2
FILES_AFTER_2S_BEFORE_RESTART 2
CURRENT_SEARCH_COUNT 0
CURRENT_EVENT_KINDS []
CURRENT_FILES 0
RETENTION_LOG_LINES
... "Bash events retention cleanup started (retention: 1s)"
... "Deleted 2 bash event file(s) older than 20260523144509"
This shows normal bash execution still writes readable events, and after restart the configured retention cleanup removes the aged event files.
Test 2: Periodic cleanup while the PR server remains running
Started a PR server with OH_BASH_EVENTS_RETENTION_SECONDS=1, executed a real bash command, then waited 65 seconds so the default max(60, retention / 2) cleanup interval could elapse:
PERIODIC_DELETE_STATUS 200
PERIODIC_EXEC_STATUS 200
PERIODIC_EXEC_STDOUT 'qa-retention-periodic
'
PERIODIC_SEARCH_COUNT_AFTER_EXEC 2
PERIODIC_FILES_AFTER_EXEC 2
WAITING_SECONDS_FOR_DEFAULT_CLEANUP_INTERVAL 65
PERIODIC_SEARCH_COUNT_AFTER_WAIT 0
PERIODIC_FILES_AFTER_WAIT 0
PERIODIC_RETENTION_LOG_LINES
... "Bash events retention cleanup started (retention: 1s)"
... "Deleted 2 bash event file(s) older than 20260523144705"
This verifies the core feature without relying on restart: aged bash event files are removed automatically by the background retention loop.
Test 3: Default behavior remains keep-forever
Started the PR server without OH_BASH_EVENTS_RETENTION_SECONDS, executed a bash command, waited, restarted, then searched bash events again:
DEFAULT_DELETE_STATUS 200
DEFAULT_EXEC_STATUS 200
DEFAULT_EXEC_STDOUT 'qa-retention-default
'
DEFAULT_SEARCH_COUNT_AFTER_EXEC 2
DEFAULT_FILES_AFTER_EXEC 2
DEFAULT_SEARCH_COUNT_AFTER_RESTART 2
DEFAULT_EVENT_KINDS_AFTER_RESTART ['BashCommand', 'BashOutput']
DEFAULT_STDOUT_PRESENT_AFTER_RESTART True
DEFAULT_FILES_AFTER_RESTART 2
This confirms the default remains indefinite retention when the new setting is unset.
Issues Found
None.
This QA review was created by an AI agent (OpenHands) on behalf of the user.
- config.py: extend bash_events_retention_seconds description with the operator warning about setting retention higher than the longest command timeout; operators reading the env var docs won't see the PR body - bash_service.py: add brief asyncio.sleep(min(interval, 60)) after a cleanup error to prevent continuous warning log flooding on persistent failures such as permission errors or a full disk - api.py: read config from api.state.config instead of get_default_config() so that create_app(Config(bash_events_retention_seconds=N)) correctly starts the retention task rather than silently falling back to the process-global environment config Co-authored-by: openhands <openhands@all-hands.dev>
all-hands-bot
left a comment
There was a problem hiding this comment.
Clean, well-scoped feature. All previously raised issues (event-loop blocking, task leak, async on a sync method, sleep-before-cleanup ordering, missing config warning, missing error backoff, and config wiring using get_default_config() instead of api.state.config) have been correctly resolved in the two fix commits. The implementation is solid and test coverage is good.
One minor observation worth addressing before merge (see inline).
This review was generated by an AI agent (OpenHands) on behalf of the user through OpenHands Automation.
Co-authored-by: OpenHands Bot <contact@all-hands.dev>
all-hands-bot
left a comment
There was a problem hiding this comment.
Clean, well-scoped feature with solid implementation and good test coverage. The two fix commits correctly resolved all previously raised issues: blocking I/O dispatched via asyncio.to_thread, task creation placed inside async with service: to prevent leaks, cleanup-before-sleep ordering, proper api.state.config wiring, error back-off, and the operator warning in the field description.
One minor observation inline. The previously-open suggestion in comment #3293546205 (asking for a clarifying comment on the double-sleep behavior) is already implemented in the current code (# Always sleep the full interval after the error back-off, so total wait on error = min(interval, 60) + interval ≈ 2× normal cadence.), so that thread can be resolved.
Overall the PR is in good shape for merge.
This review was generated by an AI agent (OpenHands) on behalf of the user via OpenHands Automation.
Co-authored-by: OpenHands Bot <contact@all-hands.dev>
all-hands-bot
left a comment
There was a problem hiding this comment.
✅ QA Report: PASS
Started the actual agent-server, verified stale bash event files are purged only on the PR branch when OH_BASH_EVENTS_RETENTION_SECONDS is set, and confirmed the bash API still works with retention enabled.
Does this PR achieve its stated goal?
Yes. The PR’s goal is to bound workspace/bash_events/ growth by adding bash_events_retention_seconds/OH_BASH_EVENTS_RETENTION_SECONDS and a background cleanup task. In a before/after run of the real server, main left a stale bash event file on disk, while commit fa9b780 deleted the stale file on startup and retained the non-stale file; a real execute_bash_command HTTP call also succeeded with retention enabled.
| Phase | Result |
|---|---|
| Environment Setup | ✅ make build completed successfully and installed dependencies in .venv. |
| CI Status | 🟡 PR rollup showed 31 successful checks, 2 skipped checks, and the current QA/review workflows in progress. |
| Functional Verification | ✅ Real server startup cleanup and bash API execution/search behavior verified. |
Functional Verification
Test 1: Stale bash event cleanup on server startup
Step 1 — Reproduce / establish baseline without the fix:
Ran the server from origin/main with the new env var set and two pre-existing bash-event-like files in the default workspace/bash_events/ directory:
git checkout origin/main
mkdir -p /tmp/qa-bash-retention-main/workspace/bash_events
printf 'old event placeholder\n' > /tmp/qa-bash-retention-main/workspace/bash_events/20200101000000_old-event.json
printf 'future event placeholder\n' > /tmp/qa-bash-retention-main/workspace/bash_events/29990101000000_future-event.json
(cd /tmp/qa-bash-retention-main && \
OPENHANDS_SUPPRESS_BANNER=1 \
OH_BASH_EVENTS_RETENTION_SECONDS=1 \
uv run --project "$ROOT" agent-server --host 127.0.0.1 --port 8123)
curl -fsS http://127.0.0.1:8123/server_info
sleep 2
find /tmp/qa-bash-retention-main/workspace/bash_events -maxdepth 1 -type f -printf '%f\n' | sortObserved:
main ready=1
{
"title": "OpenHands Agent Server",
"version": "1.23.0",
"docs": "/docs",
"redoc": "/redoc"
}
main bash_events after startup+2s:
20200101000000_old-event.json
29990101000000_future-event.json
main cleanup-related logs:
This establishes the baseline: the server starts, but setting OH_BASH_EVENTS_RETENTION_SECONDS=1 on main does not clean up old bash event files.
Step 2 — Apply the PR's changes:
Checked out PR commit fa9b780dd79b52bc3a3af9eb4117be64451e738c and repeated the same server startup with the same env var and the same stale/new file setup.
Step 3 — Re-run with the fix in place:
Ran the same verification against the PR branch:
git checkout fa9b780dd79b52bc3a3af9eb4117be64451e738c
mkdir -p /tmp/qa-bash-retention-pr/workspace/bash_events
printf 'old event placeholder\n' > /tmp/qa-bash-retention-pr/workspace/bash_events/20200101000000_old-event.json
printf 'future event placeholder\n' > /tmp/qa-bash-retention-pr/workspace/bash_events/29990101000000_future-event.json
(cd /tmp/qa-bash-retention-pr && \
OPENHANDS_SUPPRESS_BANNER=1 \
OH_BASH_EVENTS_RETENTION_SECONDS=1 \
uv run --project "$ROOT" agent-server --host 127.0.0.1 --port 8124)
curl -fsS http://127.0.0.1:8124/server_info
sleep 2
find /tmp/qa-bash-retention-pr/workspace/bash_events -maxdepth 1 -type f -printf '%f\n' | sortObserved:
pr ready=1
{
"title": "OpenHands Agent Server",
"version": "1.23.0",
"docs": "/docs",
"redoc": "/redoc"
}
pr bash_events after startup+2s:
29990101000000_future-event.json
pr cleanup-related logs:
Bash events retention cleanup started (retention: 1s)
Deleted 1 bash event file(s) older than 20260525135138
This confirms the PR behavior: with the retention env var configured, the real server starts a cleanup task, deletes the stale event file, and leaves the newer file in place.
Test 2: Bash API still works with retention enabled
Step 1 — Establish the runtime path to exercise:
With the PR applied, started the actual server with retention enabled:
OPENHANDS_SUPPRESS_BANNER=1 \
OH_BASH_EVENTS_RETENTION_SECONDS=60 \
uv run --project "$ROOT" agent-server --host 127.0.0.1 --port 8126
curl -fsS http://127.0.0.1:8126/server_infoObserved:
ready=1
Bash events retention cleanup started (retention: 60s)
This shows the changed configuration path is active on a running server.
Step 2 — Exercise the real bash API:
Ran a real HTTP command execution and then searched bash events:
curl -fsS -X POST http://127.0.0.1:8126/api/bash/execute_bash_command \
-H 'Content-Type: application/json' \
-d '{"command":"printf QA_RETENTION_OK","timeout":5}'
curl -fsS 'http://127.0.0.1:8126/api/bash/bash_events/search?limit=10'
find /tmp/qa-bash-api-pr-run/workspace/bash_events -maxdepth 1 -type f -printf '%f\n' | sortObserved:
execute_bash_command response:
{
"kind": "BashOutput",
"exit_code": 0,
"stdout": "QA_RETENTION_OK",
"stderr": null,
"order": 0,
"command_id": "70c9333aa35d40b8b8241acc6b5624b9"
}
bash_events search summary:
BashCommand printf QA_RETENTION_OK exit= None
BashOutput QA_RETENTION_OK exit= 0
item_count= 2
files written:
20260525135236_BashCommand_70c9333aa35d40b8b8241acc6b5624b9
20260525135236_BashOutput_70c9333aa35d40b8b8241acc6b5624b9_25031475be794885b67dfe4bc809fcbf
This confirms the user-facing bash command and event search flow still works while retention is enabled: the command returns exit code 0, emits the expected output, is discoverable through the search API, and writes event files.
Issues Found
None.
This QA review was generated by an AI agent (OpenHands) on behalf of the user.
all-hands-bot
left a comment
There was a problem hiding this comment.
🟢 Good taste — Clean, well-scoped feature solving a real disk-growth problem. All issues raised in prior review rounds have been correctly resolved.
Prior issues — all fixed:
- ✅ Blocking filesystem I/O dispatched via
asyncio.to_thread - ✅
delete_events_older_thanis a plain sync method (no spuriousasync) - ✅ Cleanup runs before the first sleep (restart safety)
- ✅
retention_taskcreated insideasync with service:(no task leak on aenter failure) - ✅ Config reads from
api.state.configrather thanget_default_config() - ✅ Error back-off added; double-sleep documented with an inline comment
- ✅
unlink(missing_ok=True)handles concurrent deletion - ✅ Field description includes the in-flight command operator warning
Correctness check on filename comparison:
The path.name >= cutoff_str comparison is correct. Filenames are prefixed YYYYMMDDHHMMSS_kind_...; when a file's timestamp exactly matches the cutoff, the longer string compares greater than the pure 14-char cutoff string, so the boundary file is kept (semantically "older than", not "at or older than"). Conservative and correct.
Test coverage: Solid — unit tests for delete/keep/empty/count and an end-to-end loop test using a short interval to avoid the 60 s default wait.
[RISK ASSESSMENT]
- [Overall PR]
⚠️ Risk Assessment: 🟢 LOW — Optional feature, disabled by default (None). When enabled, it only affects old event files on disk; no API surface changes. Cancellation of the background task on shutdown is correctly awaited.
VERDICT: ✅ Worth merging — Implementation is solid, all previously raised issues resolved, and the QA runs confirm end-to-end correctness.
KEY INSIGHT: The early-exit sorted-filename scan makes delete_events_older_than O(deleted) rather than O(total), keeping the operation cheap even when most events are recent.
This review was generated by an AI agent (OpenHands) on behalf of the user through OpenHands Automation.
Improve this review? If any feedback above seems incorrect or irrelevant to this repository, you can teach the reviewer to do better by adding a
.agents/skills/custom-codereview-guide.mdfile with the/codereviewtrigger. See the customization docs for details.Was this review helpful? React with 👍 or 👎 to give feedback.
all-hands-bot
left a comment
There was a problem hiding this comment.
🟡 Taste Rating: Acceptable. One config-wiring issue needs attention before approval; see inline.
Risk: 🟡 Medium — optional cleanup, but programmatic config can point retention at the wrong bash event directory.
This review was generated by an AI agent (OpenHands) on behalf of the user.
Was this automated review useful? React with 👍 or 👎 to this review to help us measure review quality.
Workflow run: https://github.com/OpenHands/software-agent-sdk/actions/runs/26403830278
| retention_task: asyncio.Task | None = None | ||
| if config.bash_events_retention_seconds is not None: | ||
| retention_task = asyncio.create_task( | ||
| get_default_bash_event_service().run_retention_cleanup_loop( |
There was a problem hiding this comment.
🟠 Important: This starts cleanup on the module-level default BashEventService, which is created from get_default_config() during router import rather than from api.state.config. With create_app(Config(bash_events_dir=custom, bash_events_retention_seconds=...)), the lifespan uses the app's retention value but deletes from the singleton's default workspace/bash_events directory instead of the configured directory. Please wire the bash event service from the app config for both routes/lifespan (or otherwise initialize the singleton from this config) and add a lifespan/config test so custom bash_events_dir is honored.
Summary
Adds
bash_events_retention_secondsto the agent server config. When set, a background task in the app lifespan periodically purges bash event files older than the configured window. Addresses unbounded disk growth from high-frequency polling ofGET /bash/bash_events/search.Motivation
Every
start_bash_commandorexecute_bash_commandcall writes one or more files toworkspace/bash_events/. Polling loops that useorder__gtfor incremental reads never trigger any cleanup, so the directory grows without bound. This feature bounds that growth with no changes to the REST API.How it works
Config.bash_events_retention_secondsint | Nonefield (defaultNone= keep forever). Set viaOH_BASH_EVENTS_RETENTION_SECONDSenv var.BashEventService.delete_events_older_than(cutoff)cutoff. Uses theYYYYMMDDHHMMSS_filename prefix for early-exit sorted scanning — no file reads needed, O(deleted) not O(total).BashEventService.run_retention_cleanup_loop(retention_seconds)max(60, retention_seconds / 2)then callsdelete_events_older_than.api_lifespanyield; cancels/awaits it in thefinallyblock on shutdown.Usage
# Retain bash events for 10 minutes OH_BASH_EVENTS_RETENTION_SECONDS=600 agent-serverEdge case to note
bash_events_retention_secondsshould be set higher than the longest expected commandtimeout. A command that starts att=0has itsBashCommandfile written then; if the retention window expires before the command finishes, that file is deleted while the in-flight task still writes output chunks. The execution is unaffected, but the on-disk history for that command will be incomplete. Setting retention >= 2x max command timeout avoids this.Tests
Added to
tests/agent_server/test_bash_service.py:test_delete_events_older_than_removes_old_keeps_new— core delete/keep behaviourtest_delete_events_older_than_empty_directory— no-op on empty dirtest_delete_events_older_than_all_newer_are_skipped— all-newer early-exit pathtest_delete_events_older_than_returns_correct_count— count accuracy with mixed agestest_run_retention_cleanup_loop_purges_old_events— end-to-end loop test usinginterval_seconds=0.05to avoid 60s waitThis PR was created by an AI agent (OpenHands) on behalf of the user.
Agent Server images for this PR
• GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server
Variants & Base Images
eclipse-temurin:17-jdknikolaik/python-nodejs:python3.13-nodejs22-slimgolang:1.21-bookwormPull (multi-arch manifest)
# Each variant is a multi-arch manifest supporting both amd64 and arm64 docker pull ghcr.io/openhands/agent-server:fa9b780-pythonRun
All tags pushed for this build
About Multi-Architecture Support
fa9b780-python) is a multi-arch manifest supporting both amd64 and arm64fa9b780-python-amd64) are also available if needed