Skip to content

Commit e678401

Browse files
authored
Merge pull request #2997 from xiaods/codex/dash-none-run-fix
fix(dash): guard missing latest run in goal projection
2 parents ee4472f + f7e99f0 commit e678401

2 files changed

Lines changed: 64 additions & 1 deletion

File tree

examples/session-dash-panel-smoke.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,64 @@ def test_focus_goal_id_narrows_the_fleet() -> None:
190190
assert projection["overview"]["goal_count"] == 1
191191

192192

193+
def test_goal_without_latest_run_does_not_crash() -> None:
194+
# A goal can be present in run_history without any latest run and with no
195+
# recommended_action on its attention item (live registries do this). The
196+
# projection must not call .get() on the missing run and must surface a
197+
# clean None next_action instead of raising.
198+
status = _build_fleet_fixture()
199+
status = dict(status)
200+
run_history = dict(status.get("run_history") or {})
201+
goals = [dict(g) for g in run_history.get("goals", []) if isinstance(g, dict)]
202+
goals.append(
203+
{
204+
"id": "empty-run-goal",
205+
"domain": "loopx-platform",
206+
"status": "active-progress",
207+
"lifecycle_phase": "adapter_running",
208+
"registry_member": True,
209+
"latest_runs": [],
210+
}
211+
)
212+
run_history["goals"] = goals
213+
status["run_history"] = run_history
214+
215+
agents = [
216+
dict(a)
217+
for a in (status.get("agent_management_projection") or {}).get("agents", [])
218+
]
219+
agents.append(
220+
{
221+
"agent_id": "empty-run-agent",
222+
"agent_model": "peer_v1",
223+
"profile_role": "docs-validation",
224+
"state": "active",
225+
"next_action": None,
226+
"last_activity_at": "2026-07-06T10:15:00Z",
227+
"goal_ids": ["empty-run-goal"],
228+
}
229+
)
230+
status["agent_management_projection"] = {
231+
"schema_version": "agent_management_projection_v0",
232+
"mode": "read_only",
233+
"agents": agents,
234+
}
235+
236+
projection = build_session_dash_projection(
237+
status_payload=status,
238+
run_history=status.get("run_history"),
239+
todo_index=status.get("todo_index"),
240+
agent_management=status.get("agent_management_projection"),
241+
focus_goal_id="empty-run-goal",
242+
)
243+
session = next(s for s in projection["sessions"])
244+
goal = session["goals"][0]
245+
assert goal["goal_id"] == "empty-run-goal"
246+
assert goal["next_action"] is None
247+
assert goal["latest_run_at"] is None
248+
assert goal["latest_classification"] is None
249+
250+
193251
def test_html_renders_human_focused_sections() -> None:
194252
projection = _build_sample_projection()
195253
html = render_session_dash_html(projection)
@@ -459,6 +517,7 @@ def _request(path: str) -> str:
459517
if __name__ == "__main__":
460518
test_projection_is_read_only_and_fleet_shaped()
461519
test_focus_goal_id_narrows_the_fleet()
520+
test_goal_without_latest_run_does_not_crash()
462521
test_html_renders_human_focused_sections()
463522
test_html_escapes_and_passes_boundary_scan()
464523
test_boundary_scan_flags_private_material()

loopx/presentation/projections/session_dash.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,11 @@ def _goal_entry(
176176
)
177177
next_action = (
178178
_text(status_item.get("recommended_action"), limit=220)
179-
or _text(latest_run.get("recommended_action"), limit=220)
179+
or (
180+
_text(latest_run.get("recommended_action"), limit=220)
181+
if latest_run
182+
else None
183+
)
180184
or None
181185
)
182186

0 commit comments

Comments
 (0)