Repository navigation
Conversation
|
📁 PR Artifacts Notice This PR contains a |
Reload persisted metadata after acquiring ownership while preserving first-start fork metadata and explicitly supplied resume credentials. Co-authored-by: openhands <openhands@all-hands.dev>
85bb8d6 to
8d2dfd8
Compare
⏳ Review waiting on checksThe current head
No action is needed. The scheduled scan retries once every check on the head reports a conclusion. This is an automated check - no AI was used to generate this comment. |
all-hands-bot
left a comment
There was a problem hiding this comment.
This review was posted by an AI agent (OpenHands).
Assessment: the fix is correct, scoped to the reported bug, and backed by authentic before/after evidence.
Verified on the current head 8d2dfd88 (base 4011cb5d):
- Fix ordering is right.
EventService.start()now re-readsmeta.json(load_meta()) after the lease claim and before_scrub_persisted_credentials()andLocalConversationconstruction, so the previous owner's final metadata write is no longer replaced by the stale catalog snapshot._start_event_servicethen propagates the refreshedevent_service.storedto the subscribers, telemetry, autotitle decision and the catalog record, i.e. the refreshed copy is what subscribers and the catalog see, not just what gets persisted. - New conversations and forks are unaffected. The reload is gated on
not is_new_conversation, and the fork path passesis_new_conversation=Truewhile the create path does too, so first-start metadata is preserved. Thebase_state_existsgate is sound: a meta-only directory cannot be resumed at all (start()raises "Cannot start a new conversation without an agent"), so no resumable conversation bypasses the reload. - Resume credentials keep the profile restriction.
resume_secretsare merged after the reload and filtered against the refreshedlaunched_agent_profile(and the request-level filtering in_reuse_existing_conversation_lockedis retained), so the removed temporary mutation ofexisting_record.storeddoes not widen access. Secrets still route through the existingStoredConversation.secrets/ cipher path, and bound credentials are still scrubbed frommeta.json. - Tests. Ran the new cases in the workspace: on head
tests/agent_server/test_conversation_service.py -k reloadgives 4 passed; on base4011cb5dthe same three new cases fail (3 failed, 1 passed), so they genuinely pin the regression rather than forging the failure state. The fulltest_conversation_service.py(134) andtest_event_service.pypass.ruff check/ruff format --checkare clean on all three changed files. - Live evidence corresponds to this head. Hashed the product files at head against the
fix_product_file_sha256values in.pr/metadata-evidence.json: both match, so the two-server graceful/SIGKILL base-fail then fix-pass runs describe this exact code. - CI. The head's
statusCheckRollupisSUCCESS(Validate PR description,check-pr-artifacts).
Two non-blocking notes for the maintainer (neither is a defect in this change):
load_meta()can now raiseFileNotFoundError/ValueErrorinsidestart()whenmeta.jsonis unreadable butbase_state.jsonexists. That escapes_get_or_load_event_service_lockedand the sharedget_event_serviceFastAPI dependency independencies.py, so event/metadata routes would return an opaque 500 where the old code silently reused the in-memory snapshot (and rewrote the corrupt file). The new tests codify fail-closed as intentional and confirm the lease is released on that failure path, so I am not withholding approval; flagging it only so you can decide whether the API layer should map this to a typed error.- The branch is currently
mergeable_state=dirty: it conflicts withmainin.agents/skills/verify-agent-server/references/feature-map/F31-persistence-and-limits.md(main edited the same recipe lines). Product code merges cleanly; a rebase is needed before merge.
✅ APPROVED
Co-authored-by: openhands <openhands@all-hands.dev>
Match ConversationService startup before reopening persisted conversations in restart tests. Co-authored-by: openhands <openhands@all-hands.dev>
HUMAN:
A second Agent Server could overwrite newer conversation metadata with its stale startup cache during takeover, so we changed the load path to reload the metadata after acquiring the lease.
We verified the fix with three regression tests, real two-server tests covering graceful shutdown and SIGKILL takeover, and the full Agent Server test suite, with no new failures compared with the baseline.
AGENT:
Why
A server that starts before another owner updates a conversation retains a stale catalog snapshot. Its first runtime load claims the lease and then overwrites
meta.jsonwith that snapshot, losing the latest title, tags and stored secrets.Summary
.pr/.Issue Number
Fixes #5629
How to Test
Validated on macOS arm64, Python 3.12.13, SDK/server 1.54.0, using the workspace lock. Base:
4011cb5d4fd508964fe210df9a50af7d2f19d29d.before B; the fix retainsafter B, tags and the persistedupdated_at. Lease generations and owner PIDs are checked. The pause used to trigger loading can advance the GET timestamp, so it must not move backwards.control-agent-server map checkpass.See .pr/verification.md for evidence and limitations. The whole F31 map runner is blocked by its unrelated DeepSeek/tmux preconditions on this machine; the tracked reproducer drives the affected real APIs directly through
control-agent-server.Video/Screenshots
Nonvisual persistence fix; API/state observations are in .pr/metadata-evidence.json.
Design Doc
Small internal load-path fix; no SDK public API, REST shape, persisted schema or lease-semantics change.
Type
Notes
AI-assisted implementation and verification.