fix(kanban-dashboard): use context-local board pin in specify/decompose endpoints
The dashboard specify and decompose endpoints run as sync FastAPI threadpool handlers and pinned the active board by mutating the process-global HERMES_KANBAN_BOARD env var. Two concurrent requests for different boards race on that shared global and cross-write — the same bug class as the CLI path (#38323), now using the scoped_current_board() contextvar introduced by the CLI fix.
This commit is contained in:
@ -38,7 +38,6 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sqlite3
|
||||
import time
|
||||
from dataclasses import asdict
|
||||
@ -1620,10 +1619,12 @@ def specify_task_endpoint(
|
||||
"""
|
||||
board = _resolve_board(board)
|
||||
# Pin the board for the duration of this call so the specifier module
|
||||
# (which calls ``kb.connect()`` with no args) hits the right DB.
|
||||
prev_env = os.environ.get("HERMES_KANBAN_BOARD")
|
||||
try:
|
||||
os.environ["HERMES_KANBAN_BOARD"] = board or kanban_db.DEFAULT_BOARD
|
||||
# (which calls ``kb.connect()`` with no args) hits the right DB. Use a
|
||||
# context-local override rather than mutating the process-global
|
||||
# HERMES_KANBAN_BOARD env var — this endpoint runs in FastAPI's
|
||||
# threadpool, so two concurrent requests for different boards would
|
||||
# otherwise race on the shared env var and cross-write (issue #38323).
|
||||
with kanban_db.scoped_current_board(board or kanban_db.DEFAULT_BOARD):
|
||||
# Import lazily so a missing auxiliary client at import time
|
||||
# doesn't break plugin load.
|
||||
from hermes_cli import kanban_specify # noqa: WPS433 (intentional)
|
||||
@ -1632,11 +1633,6 @@ def specify_task_endpoint(
|
||||
task_id,
|
||||
author=(payload.author or None),
|
||||
)
|
||||
finally:
|
||||
if prev_env is None:
|
||||
os.environ.pop("HERMES_KANBAN_BOARD", None)
|
||||
else:
|
||||
os.environ["HERMES_KANBAN_BOARD"] = prev_env
|
||||
|
||||
return {
|
||||
"ok": bool(outcome.ok),
|
||||
@ -2233,19 +2229,16 @@ def decompose_task_endpoint(
|
||||
can take minutes on reasoning models.
|
||||
"""
|
||||
board = _resolve_board(board)
|
||||
prev_env = os.environ.get("HERMES_KANBAN_BOARD")
|
||||
try:
|
||||
os.environ["HERMES_KANBAN_BOARD"] = board or kanban_db.DEFAULT_BOARD
|
||||
# Context-local board pin (see specify endpoint above): this sync
|
||||
# endpoint runs in FastAPI's threadpool, so mutating the process-global
|
||||
# HERMES_KANBAN_BOARD env var would let concurrent requests for
|
||||
# different boards race and cross-write (issue #38323).
|
||||
with kanban_db.scoped_current_board(board or kanban_db.DEFAULT_BOARD):
|
||||
from hermes_cli import kanban_decompose # noqa: WPS433 (intentional)
|
||||
outcome = kanban_decompose.decompose_task(
|
||||
task_id,
|
||||
author=(payload.author or None),
|
||||
)
|
||||
finally:
|
||||
if prev_env is None:
|
||||
os.environ.pop("HERMES_KANBAN_BOARD", None)
|
||||
else:
|
||||
os.environ["HERMES_KANBAN_BOARD"] = prev_env
|
||||
|
||||
return {
|
||||
"ok": bool(outcome.ok),
|
||||
|
||||
Reference in New Issue
Block a user