fix(#5130): recompute cron snapshots in selected profile

This commit is contained in:
Rod Boev committed 2026-06-28 15:29:40 -04:00
1 parent e24e6c54a6
commit 7e806d7194
2 files changed
+416 -2

No files matched your search

+58 -2
View File
@@ -18235,6 +18235,53 @@ def _handle_chat_sync(handler, body):
)
def _selected_profile_snapshot_updates(
profile: str | None,
*,
provider,
model,
base_url=None,
no_agent=False,
) -> dict[str, str | None]:
selected_profile = str(profile or "").strip()
if not selected_profile or (provider is not None and model is not None):
return {}
try:
from api.profiles import profile_env_for_background_worker
from cron.jobs import _compute_provider_model_snapshots
except Exception:
logger.debug("Failed to load cron snapshot repair helpers", exc_info=True)
return {}
try:
with profile_env_for_background_worker(
selected_profile,
"cron create snapshot",
logger_override=logger,
):
provider_snapshot, model_snapshot = _compute_provider_model_snapshots(
provider=provider,
model=model,
base_url=base_url,
no_agent=no_agent,
)
except Exception:
logger.debug(
"Failed to compute selected-profile cron snapshots for %s",
selected_profile,
exc_info=True,
)
return {}
updates = {}
if provider is None:
updates["provider_snapshot"] = provider_snapshot
if model is None:
updates["model_snapshot"] = model_snapshot
return updates
def _handle_cron_create(handler, body):
try:
require(body, "prompt", "schedule")
@@ -18245,18 +18292,27 @@ def _handle_cron_create(handler, body):
profile = _normalize_cron_profile_value(body.get("profile"))
toast_notifications = body.get("toast_notifications") is not False
requested_model = body.get("model") or None
requested_provider = body.get("provider") or None
job = create_job(
prompt=body["prompt"],
schedule=body["schedule"],
name=body.get("name") or None,
deliver=body.get("deliver") or "local",
skills=body.get("skills") or [],
model=body.get("model") or None,
provider=body.get("provider") or None,
model=requested_model,
provider=requested_provider,
)
post_create_updates = {}
if profile is not None:
post_create_updates["profile"] = profile
post_create_updates.update(
_selected_profile_snapshot_updates(
profile,
provider=requested_provider,
model=requested_model,
)
)
if not toast_notifications:
post_create_updates["toast_notifications"] = False
if post_create_updates:
@@ -0,0 +1,358 @@
"""Regression coverage for issue #5130 cron create-time profile snapshots."""
import io
import json
import sys
import types
from contextlib import contextmanager
from pathlib import Path
import pytest
class _JSONHandler:
def __init__(self):
self.status = None
self.headers = {}
self.response_headers = []
self.wfile = io.BytesIO()
def send_response(self, status):
self.status = status
def send_header(self, key, value):
self.response_headers.append((key, value))
def end_headers(self):
pass
def _payload(handler):
return json.loads(handler.wfile.getvalue().decode("utf-8"))
def _install_fake_cron_modules(monkeypatch, cron_jobs):
cron_pkg = types.ModuleType("cron")
cron_pkg.__path__ = []
monkeypatch.setitem(sys.modules, "cron", cron_pkg)
monkeypatch.setitem(sys.modules, "cron.jobs", cron_jobs)
def test_cron_create_recomputes_unpinned_provider_snapshot_under_selected_profile(monkeypatch):
import api.profiles as profiles
import api.routes as routes
calls = []
profile_events = []
created = {
"id": "job5130",
"name": "Selected profile job",
"prompt": "ping",
"schedule": {"kind": "interval", "minutes": 60},
"provider_snapshot": "openai-api",
}
cron_jobs = types.ModuleType("cron.jobs")
cron_jobs.JOBS_FILE = Path("C:/owning/cron/jobs.json")
cron_jobs.CRON_DIR = Path("C:/owning/cron")
cron_jobs.OUTPUT_DIR = Path("C:/owning/cron/output")
def create_job(**kwargs):
calls.append(("create", kwargs))
return dict(created)
def update_job(job_id, updates):
calls.append(("update", job_id, updates))
return {**created, **updates}
def compute_snapshots(*, provider, model, base_url, no_agent):
calls.append(
(
"compute",
{
"provider": provider,
"model": model,
"base_url": base_url,
"no_agent": no_agent,
},
)
)
return "openai-codex", "gpt-5.4"
@contextmanager
def fake_profile_env(profile, purpose, logger_override=None):
profile_events.append(("enter", profile, purpose, logger_override is routes.logger))
yield
profile_events.append(("exit", profile, purpose))
cron_jobs.create_job = create_job
cron_jobs.update_job = update_job
cron_jobs._compute_provider_model_snapshots = compute_snapshots
monkeypatch.setattr(profiles, "list_profiles_api", lambda: [{"name": "research"}])
monkeypatch.setattr(profiles, "profile_env_for_background_worker", fake_profile_env)
_install_fake_cron_modules(monkeypatch, cron_jobs)
handler = _JSONHandler()
routes._handle_cron_create(
handler,
{
"name": "Selected profile job",
"prompt": "ping",
"schedule": "every 60m",
"deliver": "local",
"profile": "research",
"model": "gpt-5.4",
},
)
body = _payload(handler)
assert handler.status == 200
assert body["ok"] is True
assert calls[0] == (
"create",
{
"prompt": "ping",
"schedule": "every 60m",
"name": "Selected profile job",
"deliver": "local",
"skills": [],
"model": "gpt-5.4",
"provider": None,
},
)
assert calls[1] == (
"compute",
{
"provider": None,
"model": "gpt-5.4",
"base_url": None,
"no_agent": False,
},
)
assert calls[2] == (
"update",
"job5130",
{"profile": "research", "provider_snapshot": "openai-codex"},
)
assert profile_events == [
("enter", "research", "cron create snapshot", True),
("exit", "research", "cron create snapshot"),
]
def test_cron_create_with_blank_profile_keeps_ambient_snapshot_semantics(monkeypatch):
import api.profiles as profiles
import api.routes as routes
calls = []
profile_events = []
created = {
"id": "job5130",
"name": "Ambient job",
"prompt": "ping",
"schedule": {"kind": "interval", "minutes": 60},
"provider_snapshot": "openai-api",
}
cron_jobs = types.ModuleType("cron.jobs")
cron_jobs.create_job = lambda **kwargs: calls.append(("create", kwargs)) or dict(created)
cron_jobs.update_job = lambda *args, **kwargs: pytest.fail("blank profile should not update the job")
cron_jobs._compute_provider_model_snapshots = (
lambda **kwargs: pytest.fail("blank profile should not recompute snapshots")
)
@contextmanager
def fake_profile_env(*args, **kwargs):
profile_events.append(("enter", args, kwargs))
yield
monkeypatch.setattr(profiles, "list_profiles_api", lambda: [{"name": "research"}])
monkeypatch.setattr(profiles, "profile_env_for_background_worker", fake_profile_env)
_install_fake_cron_modules(monkeypatch, cron_jobs)
handler = _JSONHandler()
routes._handle_cron_create(
handler,
{
"name": "Ambient job",
"prompt": "ping",
"schedule": "every 60m",
"deliver": "local",
"profile": "",
"model": "gpt-5.4",
},
)
body = _payload(handler)
assert handler.status == 200
assert body["ok"] is True
assert body["job"]["provider_snapshot"] == "openai-api"
assert calls == [
(
"create",
{
"prompt": "ping",
"schedule": "every 60m",
"name": "Ambient job",
"deliver": "local",
"skills": [],
"model": "gpt-5.4",
"provider": None,
},
)
]
assert profile_events == []
def test_cron_create_with_explicit_provider_and_model_skips_snapshot_override(monkeypatch):
import api.profiles as profiles
import api.routes as routes
calls = []
created = {
"id": "job5130",
"name": "Pinned job",
"prompt": "ping",
"schedule": {"kind": "interval", "minutes": 60},
"provider_snapshot": "openai-codex",
"model_snapshot": "gpt-5.4",
}
cron_jobs = types.ModuleType("cron.jobs")
cron_jobs.create_job = lambda **kwargs: calls.append(("create", kwargs)) or dict(created)
cron_jobs.update_job = lambda job_id, updates: calls.append(("update", job_id, updates)) or {
**created,
**updates,
}
cron_jobs._compute_provider_model_snapshots = (
lambda **kwargs: pytest.fail("explicit provider/model should not recompute snapshots")
)
@contextmanager
def fake_profile_env(*args, **kwargs):
pytest.fail("explicit provider/model should not enter selected-profile snapshot context")
yield
monkeypatch.setattr(profiles, "list_profiles_api", lambda: [{"name": "research"}])
monkeypatch.setattr(profiles, "profile_env_for_background_worker", fake_profile_env)
_install_fake_cron_modules(monkeypatch, cron_jobs)
handler = _JSONHandler()
routes._handle_cron_create(
handler,
{
"name": "Pinned job",
"prompt": "ping",
"schedule": "every 60m",
"deliver": "local",
"profile": "research",
"provider": "openai-codex",
"model": "gpt-5.4",
},
)
body = _payload(handler)
assert handler.status == 200
assert body["ok"] is True
assert calls == [
(
"create",
{
"prompt": "ping",
"schedule": "every 60m",
"name": "Pinned job",
"deliver": "local",
"skills": [],
"model": "gpt-5.4",
"provider": "openai-codex",
},
),
("update", "job5130", {"profile": "research"}),
]
def test_selected_profile_snapshot_helper_never_repoints_cron_store_globals(monkeypatch):
import api.profiles as profiles
from api.routes import _selected_profile_snapshot_updates
profile_events = []
cron_jobs = types.ModuleType("cron.jobs")
cron_jobs.JOBS_FILE = Path("C:/owning/cron/jobs.json")
cron_jobs.CRON_DIR = Path("C:/owning/cron")
cron_jobs.OUTPUT_DIR = Path("C:/owning/cron/output")
original_paths = (
cron_jobs.JOBS_FILE,
cron_jobs.CRON_DIR,
cron_jobs.OUTPUT_DIR,
)
def compute_snapshots(*, provider, model, base_url, no_agent):
profile_events.append(
(
"compute",
cron_jobs.JOBS_FILE,
cron_jobs.CRON_DIR,
cron_jobs.OUTPUT_DIR,
provider,
model,
base_url,
no_agent,
)
)
return "openai-codex", "gpt-5.4"
@contextmanager
def fake_profile_env(profile, purpose, logger_override=None):
profile_events.append(
("enter", profile, purpose, cron_jobs.JOBS_FILE, cron_jobs.CRON_DIR, cron_jobs.OUTPUT_DIR)
)
yield
profile_events.append(
("exit", profile, purpose, cron_jobs.JOBS_FILE, cron_jobs.CRON_DIR, cron_jobs.OUTPUT_DIR)
)
cron_jobs._compute_provider_model_snapshots = compute_snapshots
monkeypatch.setattr(profiles, "profile_env_for_background_worker", fake_profile_env)
_install_fake_cron_modules(monkeypatch, cron_jobs)
updates = _selected_profile_snapshot_updates(
"research",
provider=None,
model=None,
)
assert updates == {
"provider_snapshot": "openai-codex",
"model_snapshot": "gpt-5.4",
}
assert (cron_jobs.JOBS_FILE, cron_jobs.CRON_DIR, cron_jobs.OUTPUT_DIR) == original_paths
assert profile_events == [
(
"enter",
"research",
"cron create snapshot",
original_paths[0],
original_paths[1],
original_paths[2],
),
(
"compute",
original_paths[0],
original_paths[1],
original_paths[2],
None,
None,
None,
False,
),
(
"exit",
"research",
"cron create snapshot",
original_paths[0],
original_paths[1],
original_paths[2],
),
]