#1380
Hub startup spends seconds to minutes in managed-thread PMA recovery after restart
Observed on the hub process (codex-autorunner hub serve) after restart: CPU spikes to ~98-100% during deferred startup, and the startup phase repeatedly spends most of its time in managed_thread_restore.
Evidence:
- Hub log shows repeated deferred startup runs with large
managed_thread_restoredurations: 6.8s, 12.4s, 23.5s, 93.0s, 105.5s, 55.5s, etc. - The same log shows
discord.turn.progress_reconcile_startup_finishedafter startup, which suggests the Discord PMA recovery path is also active on restart. - A live
sampleof the hot hub PID showed time in JSON parsing and SQLite execution, not an obvious recursion stack. That points to expensive startup/state scanning rather than a tight call recursion.
Likely hot paths:
src/codex_autorunner/surfaces/web/app.pydeferred startup:recover_orphaned_managed_thread_executions(),restart_managed_thread_queue_workers(),reap_managed_processes(), andmount_manager.start_repo_lifespans().src/codex_autorunner/surfaces/web/routes/pma_routes/managed_thread_runtime_control.py:recover_orphaned_executions()scanslist_thread_ids_with_running_executions();restart_queue_workers()scanslist_thread_ids_with_pending_queue().src/codex_autorunner/integrations/discord/service.py: startup reconciliation runsreconcile_discord_turn_progress_leases()and_resume_interaction_recovery().src/codex_autorunner/integrations/discord/message_turns.py:reconcile_discord_turn_progress_leases()walks leases and may perform per-lease lookups + message edits.
Existing tests that exercise pieces of this path:
tests/test_hub_issue_1266.py::test_hub_health_serves_before_deferred_startup_work_finishestests/surfaces/web/routes/pma_routes/test_managed_thread_runtime.py::test_restart_managed_thread_queue_workers_restores_pending_threadstests/surfaces/web/routes/pma_routes/test_managed_thread_runtime.py::test_recover_orphaned_managed_thread_executions_unblocks_restart_queuetests/surfaces/web/routes/pma_routes/test_managed_thread_runtime.py::test_recover_orphaned_managed_thread_executions_recovers_pma_runs_on_chat_bound_threadstests/test_discord_message_turn_background_failures.py::test_startup_reconciles_orphaned_progress_leasestests/test_discord_message_turn_background_failures.py::test_startup_reconciles_progress_leases_stuck_in_retiring_state
Gap:
- I do not yet have a repro that proves a specific infinite loop or re-entrant restart cycle. Current evidence looks more like repeated expensive scans / recovery passes on restart, amplified by the size of orchestration state.
Additional evidence from local profiling/state inspection:
/Users/dazheng/car-workspace/.codex-autorunner/orchestration.sqlite3is2.1 GB.dbstatshowsorch_event_projectionsalone is about1789.4 MB, with indexes adding another~325 MB.orch_event_projectionsis almost entirelyevent_family='turn.timeline':1,610,583rows /1211.7 MB.output_delta:1,530,177rows /739.0 MBrun_notice:45,769rows /445.6 MBf6a7def0-1a2a-4edd-8626-ba3c857b18f1) alone accounts for35,811timeline rows /413.9 MB, of which34,096arerun_noticerows averaging12.3 KBeach.run_noticepayload contains a long model thinking/audit message body, not just metadata. The same string fragment ("Now I have all the data. Let me compile a comprehensive audit...") appears in10,137persistedrun_noticerows for that single execution.This suggests a second amplifier beyond restart-time scans: we are persisting large, repeated notice/thinking bodies into
turn.timeline, likely without dedupe/compaction and possibly with cumulative text rather than small deltas. That both inflates the DB and makes restart recovery / resume / timeline reads progressively more expensive.Relevant code paths to inspect next:
src/codex_autorunner/core/orchestration/turn_timeline.py(persist_turn_timelinestores fullRunNotice.messageintopayload_json)src/codex_autorunner/core/orchestration/runtime_thread_events.py(mapsagent_thought_chunk/ ACP progress intoRunNotice)src/codex_autorunner/core/acp_lifecycle.pyand agent backends that may surface cumulative thinking/progress text rather than bounded deltas.