fix(api_server): persist and round-trip session pinned/archived via PATCH - #76007
fix(api_server): persist and round-trip session pinned/archived via PATCH#76007SongotenU wants to merge 1 commit into
Conversation
7b912e9 to
593bae4
Compare
pestoura
left a comment
There was a problem hiding this comment.
The new PATCH fields need strict boolean validation before persistence. bool(body["pinned"]) and bool(body["archived"]) silently reinterpret valid JSON values of the wrong type: for example, {"pinned": "false"} and {"archived": 1} both persist as true. That makes malformed clients mutate session state instead of receiving a 400, and the response then confirms the unintended value. Please reject non-bool values for both fields (not merely truthy/falsy values) and add regression cases for at least a string and a number, while retaining the existing explicit false toggle coverage.
teknium1
left a comment
There was a problem hiding this comment.
Thanks for closing the API-server session-metadata parity gap; current main still limits PATCH to title/end_reason at gateway/platforms/api_server.py:3241 and omits both fields from _session_response at gateway/platforms/api_server.py:3023.
Problems
apps/desktop/src/store/session-pin-sync.ts:86is unreachable. The existing guard at lines 80-81 alreadycontinues when an outstandingfalsewrite conflicts withrow.pinned === true, soawaited === falsecannot reach the new branch.- The new mocked endpoint tests do not cover the changed pinned-page behavior at
gateway/platforms/api_server.py:3093. Please add a real-SessionDBAPI test alongside the existing session endpoint fixture intests/gateway/test_session_api.py:14-51, including an out-of-page pinned row and false-value round trips.
Suggested changes
- Remove the redundant desktop guard and cover the API-server list/PATCH contract through the real SessionDB fixture.
Automated hermes-sweeper review.
| @@ -82,6 +82,10 @@ function pullRemotePins(): void { | |||
| } | |||
|
|
|||
| if (row.pinned && !heldLocally) { | |||
| // If we just issued an unpin for this id, wait for it to settle. | |||
| if (awaited !== undefined && awaited === false) { | |||
There was a problem hiding this comment.
This condition is unreachable: the existing check immediately above continues whenever awaited === false and row.pinned === true. Please remove this redundant branch.
| @@ -2618,3 +2620,87 @@ def __init__(self, **kwargs): | |||
| assert captured[1]["model"] == "minimax/minimax-m3" | |||
|
|
|||
|
|
|||
| # --------------------------------------------------------------------------- | |||
| # PATCH /api/sessions/{session_id} — pinned / archived metadata | |||
| # --------------------------------------------------------------------------- | |||
There was a problem hiding this comment.
Please add this endpoint coverage to tests/gateway/test_session_api.py using its real SessionDB fixture, and exercise the changed include_pinned=True list behavior. These mocks verify handler calls but not SessionDB persistence or pinned-row back-fill.
…ATCH
The desktop pin/unpin + archive surfaces PATCH /api/sessions/{id} with
{"pinned"} and {"archived"}, but the gateway rejected both fields
(unsupported_session_field 400) and never returned them in the session
payload, so session-pin-sync.ts pull/reconcile passes saw stale state.
- Accept pinned/archived in PATCH allowed fields and persist them
- Include pinned/archived in the client-safe session response keys
- Pass include_pinned=True when listing sessions so old pins survive
pagination instead of silently dropping out of the sidebar
- Guard the pull pass against re-pinning an id we just unpinned, and
surface unpin failures instead of swallowing them
Adds TestSessionPatchEndpoint covering pin/archive persistence,
unknown-field rejection, and the False (unpin/unarchive) toggle path.
593bae4 to
a102b60
Compare
|
Thanks for the reviews — both points addressed. @pestoura — strict boolean validation (done): @teknium1 — dead desktop guard (done): @teknium1 — real-SessionDB API tests (done):
This exposed a real bug the mocked tests were hiding: SQLite stores these flags as 0/1, so the response was leaking Verification: |
pestoura
left a comment
There was a problem hiding this comment.
Follow-up on the boolean-coercion finding: the current head resolves it. Both PATCH fields now require an actual JSON boolean before persistence, malformed string/number values return 400 invalid_field_type, and the explicit false path remains covered. The added real-SessionDB round-trip also verifies that SQLite's 0/1 representation is serialized back as JSON booleans. CI run 30691886241 completed successfully on this head. No remaining concern from my previous review.
|
Superseded by #80711. |
Summary
The desktop pin/unpin + archive surfaces PATCH
/api/sessions/{id}with{"pinned"}and{"archived"}(apps/desktop/src/store/session-pin-sync.ts,use-session-actions/index.ts:1429,sessions-settings.tsx:74), but the gateway rejected both fields with400 unsupported_session_fieldand never returned them in the session payload — so the client's pull/reconcile passes saw stale state and the "rows now carry pinned" pull path was effectively dead code.This fixes the whole bug class, not just one call site:
_handle_patch_session— acceptpinned/archivedin the PATCH allow-list and persist them viaset_session_pinned/set_session_archived(using"pinned" in body, not truthiness, so unpin/unarchive withfalseactually reaches the DB)._session_response— includepinned/archivedin the client-safe keys so the response round-trips the new state._handle_list_sessions— passinclude_pinned=Trueso pinned sessions survive pagination instead of silently dropping out of the sidebar (the backfill logic already exists inlist_sessions_rich).session-pin-sync.ts— guard the pull pass against re-pinning an id that was just unpinned, and surface unpin failures instead of swallowing them.Tests
TestSessionPatchEndpointintests/gateway/test_api_server.py: pin/archive persistence, unknown-field rejection, and thefalse(unpin/unarchive) toggle path.scripts/run_tests.sh tests/gateway/test_api_server.py tests/test_hermes_state.py— 235 passed, 0 failed.npx vitest run src/store/session-pin-sync.test.ts— 11 passed.npx tsc --noEmit— clean.