mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-27 06:11:40 +00:00
fix(kanban): guard task_age against corrupt created_at values like '%s'
task_age() crashed with ValueError when created_at contained the literal format string '%s' instead of a Unix timestamp, taking down the entire GET /board endpoint with a 500. - Add _safe_int() helper that returns None on non-numeric values - Refactor task_age() to use _safe_int instead of bare int() casts - Wrap task_age() call in _task_dict with try/except fallback so one corrupt row never kills the whole board endpoint
This commit is contained in:
parent
c39168453d
commit
061a183008
2 changed files with 20 additions and 7 deletions
|
|
@ -4190,16 +4190,26 @@ def board_stats(conn: sqlite3.Connection) -> dict:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_int(val: Optional[str]) -> Optional[int]:
|
||||||
|
"""Parse a timestamp field to int, returning None on garbage like '%s'."""
|
||||||
|
if val is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return int(val)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def task_age(task: Task) -> dict:
|
def task_age(task: Task) -> dict:
|
||||||
"""Return age metrics for a single task. All values are seconds or None."""
|
"""Return age metrics for a single task. All values are seconds or None."""
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
age_since_created = now - int(task.created_at) if task.created_at else None
|
created = _safe_int(task.created_at)
|
||||||
age_since_started = (
|
started = _safe_int(task.started_at)
|
||||||
now - int(task.started_at) if task.started_at else None
|
completed = _safe_int(task.completed_at)
|
||||||
)
|
age_since_created = now - created if created else None
|
||||||
|
age_since_started = now - started if started else None
|
||||||
time_to_complete = (
|
time_to_complete = (
|
||||||
int(task.completed_at) - int(task.started_at or task.created_at)
|
completed - (started or created) if completed else None
|
||||||
if task.completed_at else None
|
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
"created_age_seconds": age_since_created,
|
"created_age_seconds": age_since_created,
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,10 @@ def _task_dict(
|
||||||
d = asdict(task)
|
d = asdict(task)
|
||||||
# Add derived age metrics so the UI can colour stale cards without
|
# Add derived age metrics so the UI can colour stale cards without
|
||||||
# computing deltas client-side.
|
# computing deltas client-side.
|
||||||
d["age"] = kanban_db.task_age(task)
|
try:
|
||||||
|
d["age"] = kanban_db.task_age(task)
|
||||||
|
except Exception:
|
||||||
|
d["age"] = {"created_age_seconds": None, "started_age_seconds": None, "time_to_complete_seconds": None}
|
||||||
# Surface the latest non-null run summary so dashboards don't show
|
# Surface the latest non-null run summary so dashboards don't show
|
||||||
# blank cards/drawers for tasks where the worker handed off via
|
# blank cards/drawers for tasks where the worker handed off via
|
||||||
# ``task_runs.summary`` (the kanban-worker pattern) instead of
|
# ``task_runs.summary`` (the kanban-worker pattern) instead of
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue