HERMES-GIELIE/api
ai-ag2026 d505b45545 fix(bg): serialize daemon-thread start under a lifecycle lock
start_drain_thread and start_session_channel_reaper checked is_alive()
and then created + started the daemon thread without holding a lock, a
check-then-act race: two concurrent callers could both see "not alive"
and each spawn a thread. The loser's thread was never stored in the
module global and ran forever, un-joinable by the matching stop_*. Both
check-then-start sequences now run under a dedicated
_THREAD_LIFECYCLE_LOCK (kept separate from the purpose-bound
SESSION_CHANNELS_LOCK / _EMIT_COALESCE_LOCK), so exactly one thread is
ever created.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 20:58:18 +00:00
..
__init__.py
agent_health.py
agent_sessions.py
auth.py
auth_oidc.py
background.py
background_process.py fix(bg): serialize daemon-thread start under a lifecycle lock 2026-07-08 20:58:18 +00:00
clarify.py
commands.py
compression_anchor.py
compression_recovery.py
config.py
crash_visibility.py
dashboard_probe.py
extensions.py
gateway_chat.py
gateway_restart.py
gateway_watcher.py
goals.py
helpers.py
kanban_bridge.py
metering.py
models.py
oauth.py
office_documents.py
onboarding.py
passkeys.py
paths.py
plugin_providers.py
plugins.py
profiles.py
providers.py
request_diagnostics.py
rollback.py
route_approvals.py
route_session_list_cache.py
routes.py
run_journal.py
runner_client.py
runtime_adapter.py
session_discoverability.py
session_events.py
session_export_html.py
session_lifecycle.py
session_ops.py
session_recovery.py
skill_usage.py
sse_chunked.py
startup.py
state_sync.py
streaming.py
system_health.py
terminal.py
todo_state.py
turn_journal.py
updates.py
upload.py
usage.py
webui_session_db.py
workspace.py
workspace_git.py
worktrees.py