diff --git a/api/providers.py b/api/providers.py index 6b855f69..8dfe6314 100644 --- a/api/providers.py +++ b/api/providers.py @@ -1393,16 +1393,20 @@ def provider_has_process_wakeup_recovery_credential(provider_id: str, *, refresh if not configured_fingerprint: return False has_unusable_pool_entry = False + has_unknown_unusable_pool_entry = False for entry in _pool_entry_payloads(provider): entry_fingerprint = _entry_secret_fingerprint(entry) if not _pool_entry_currently_unusable(entry): - if entry_fingerprint == configured_fingerprint: + if entry_fingerprint and entry_fingerprint == configured_fingerprint: return True continue has_unusable_pool_entry = True + if not entry_fingerprint: + has_unknown_unusable_pool_entry = True + continue if entry_fingerprint == configured_fingerprint: return False - return has_unusable_pool_entry + return has_unusable_pool_entry and not has_unknown_unusable_pool_entry def _active_provider_id() -> str | None: diff --git a/tests/test_issue3929_process_wakeup_pause.py b/tests/test_issue3929_process_wakeup_pause.py index 2aabcae2..d99ead3f 100644 --- a/tests/test_issue3929_process_wakeup_pause.py +++ b/tests/test_issue3929_process_wakeup_pause.py @@ -625,6 +625,59 @@ def test_process_wakeup_pause_keeps_pause_when_config_key_has_no_pool_evidence(t assert saved.process_wakeup_pause["suppressed_count"] == 1 +def test_process_wakeup_pause_keeps_pause_when_unusable_pool_entry_lacks_fingerprint(tmp_path, monkeypatch): + session = Session( + session_id="wakeup_pause_unusable_pool_entry_lacks_fingerprint", + workspace=str(tmp_path), + model="test-model", + model_provider="test-provider", + ) + pause = models.record_process_wakeup_provider_unavailable_pause( + session, + classification="credential_pool_empty", + model="test-model", + provider="test-provider", + ) + assert pause is not None + session.save() + models.SESSIONS[session.session_id] = session + monkeypatch.setattr(providers, "_get_provider_api_key", lambda _provider: "generic-config-key") + monkeypatch.setattr( + providers, + "_pool_entry_payloads", + lambda _provider: [{"last_status": "dead"}], + ) + monkeypatch.setattr( + routes, + "provider_has_process_wakeup_recovery_credential", + providers.provider_has_process_wakeup_recovery_credential, + ) + + def _unexpected_start_run(*_args, **_kwargs): + raise AssertionError("unknown exhausted pool secret must not prove recovery") + + monkeypatch.setattr(routes, "_resolve_chat_workspace_with_recovery", lambda _s, _w: str(tmp_path)) + monkeypatch.setattr(routes, "_read_profile_model_config", lambda _s, _p: (None, None, {})) + monkeypatch.setattr( + routes, + "_resolve_compatible_session_model_state", + lambda *_args, **_kwargs: ("test-model", "test-provider", False), + ) + monkeypatch.setattr(routes, "_start_run", _unexpected_start_run) + + response = routes.start_session_turn( + session.session_id, + "[IMPORTANT: Background process completed with unknown exhausted pool key.]", + source="process_wakeup", + ) + + assert response["_status"] == 409 + assert response["error"] == PROCESS_WAKEUP_PAUSE_ERROR + saved = Session.load(session.session_id) + assert saved is not None + assert saved.process_wakeup_pause["suppressed_count"] == 1 + + def test_process_wakeup_pause_clears_when_config_key_differs_from_exhausted_pool_secret(tmp_path, monkeypatch): session = Session( session_id="wakeup_pause_config_key_differs_from_exhausted_pool",