fix(gateway): a repeated /model --once keeps the earliest restore target

`/model X --once` then `/model Y --once` before any turn replaced the
pending snapshot with one taken while X was live, so slot cleanup restored
X and made the first temporary model permanent (ehz0ah, review on #106966).
setdefault keeps the snapshot from the first command — the user's standing
override — as the restore target. One producer-driven regression test.
This commit is contained in:
kshitijk4poor
2026-09-11 12:25:46 +05:30
committed by kshitij
parent c71a411fe5
commit 90970d85f2
2 changed files with 20 additions and 2 deletions

View File

@@ -269,8 +269,10 @@ class GatewayModelCommandsMixin:
if one_turn:
if not hasattr(self, "_pending_one_turn_model_restores"):
self._pending_one_turn_model_restores = {}
self._pending_one_turn_model_restores[ctx.session_key] = dict(
ctx.restore_snapshot or {"had_override": False, "override": None}
# A repeated --once before the turn runs must keep the EARLIEST snapshot: the later
# command's snapshot is the first temporary model, not the user's standing override.
self._pending_one_turn_model_restores.setdefault(
ctx.session_key, dict(ctx.restore_snapshot or {"had_override": False, "override": None}),
)
elif not picker and hasattr(self, "_pending_one_turn_model_restores"):
self._pending_one_turn_model_restores.pop(ctx.session_key, None)

View File

@@ -282,3 +282,19 @@ class TestOneTurnNeverPersisted:
# ...but NEVER written through to the persistent session store.
runner.async_session_store.set_model_override.assert_not_awaited()
@pytest.mark.asyncio
async def test_repeated_once_keeps_the_earliest_restore_target(self, tmp_path, monkeypatch):
"""`/model X --once` then `/model Y --once` before any turn: the pending snapshot must still
be the user's standing override (none here), not X — otherwise slot cleanup would make the
first temporary model permanent."""
runner = self._runner_with_store(tmp_path, monkeypatch)
sk = build_session_key(_make_source())
await runner._handle_model_command(self._event("/model gpt-5.5 --once"))
assert runner._session_model_overrides[sk]["model"] == "gpt-5.5"
await runner._handle_model_command(self._event("/model gpt-5.5 --once"))
# The second producer call snapshotted the live gpt-5.5 override; the pending restore
# must still be the ORIGINAL "no override" state.
assert runner._pending_one_turn_model_restores[sk]["had_override"] is False