hermes - ✅(Solved) Fix Kanban legacy DB migration fails creating session_id index before column exists [1 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#28698Fetched 2026-05-20 04:02:27
View on GitHub
Comments
1
Participants
2
Timeline
7
Reactions
0
Timeline (top)
labeled ×4closed ×1commented ×1cross-referenced ×1

A legacy kanban.db can fail dashboard/Kanban initialization with:

sqlite3.OperationalError: no such column: session_id

The issue appears when the existing tasks table predates the newer optional session_id column.

Error Message

sqlite3.OperationalError: no such column: session_id

Root Cause

hermes_cli/kanban_db.py::connect() runs conn.executescript(SCHEMA_SQL) before _migrate_add_optional_columns(conn).

For a legacy DB, this statement is a no-op because the table already exists:

CREATE TABLE IF NOT EXISTS tasks (... session_id TEXT ...);

But SCHEMA_SQL then immediately runs:

CREATE INDEX IF NOT EXISTS idx_tasks_session_id ON tasks(session_id);

Because _migrate_add_optional_columns() has not run yet, the legacy tasks table still has no session_id, so SQLite raises no such column: session_id before the migration gets a chance to add it.

Fresh DBs are fine; legacy DBs can fail.

Fix Action

Fixed

PR fix notes

PR #28781: fix(kanban): hoist all additive-column indexes out of SCHEMA_SQL (#28461)

Description (problem / solution / changelog)

Summary

Salvage of #28461 — fixes the Kanban DB legacy-migration crash reported in #28464 (and rediscovered in #28554, #28617, #28654, #28698) where connect() runs SCHEMA_SQL before _migrate_add_optional_columns(), so a legacy board missing any additive indexed column aborts schema init with OperationalError: no such column: <col> before the migration can add it.

The community filed six PRs for this bug (all on 2026-05-19). #28461 (itsreverence) was first and best-explained but only covered 2 of the 4 vulnerable indexes. #28754 (quocanh261997) extended coverage to 3 of the 4. This salvage combines the strengths of all the open PRs and closes the remaining gap.

What changed

Move every CREATE INDEX that depends on an additive column out of SCHEMA_SQL and create them after _migrate_add_optional_columns() adds the columns. All four additive-column indexes are now hoisted:

IndexAdditive columnIntroduced in
idx_tasks_session_idtasks.session_id#28447
idx_tasks_tenanttasks.tenant#16081
idx_tasks_idempotencytasks.idempotency_key#17805
idx_events_runtask_events.run_id#17805

Three of these were ticking landmines for any user whose board predated the column. idx_tasks_session_id was the one that bit users today because it's the most recent (#28447 merged the day before the bug reports flooded in), but the same migration trap applied to all four.

Beyond #28754's coverage:

  • idx_events_run: also hoisted out of SCHEMA_SQL and made unconditional in the migration. A legacy task_events table predating #17805 (no run_id) was still hitting the same SQLite trap that #28754 only addressed for tasks.
  • idx_tasks_idempotency: removed the redundant inner-conditional create (line ~1085) that was nested inside if "idempotency_key" not in cols:. Since the unconditional create lower in the function already runs CREATE INDEX IF NOT EXISTS, the inner one was dead code on fresh DBs once we removed the SCHEMA_SQL line.
  • Strengthened the regression test to cover all 4 indexes and to seed a pre-#17805 task_events shape that exercises the run_id migration path.
  • Added explicit rationale comments so the next contributor adding tasks.<new_col> doesn't re-introduce the trap.
  • Picked up an adjacent test fix from #28754: test_max_runtime_uses_current_run_start_after_retry now monkeypatch-es kb._pid_alive so it stops tripping tests/conftest.py's live-system guard on os.kill(<fake_pid>, 0). Pre-existing failure on origin/main, unrelated to the index ordering bug; carried in via the cherry-pick rather than split out separately.

Validation

Confirmed against origin/main:

origin/main reproduces against pre-#16081 fixture: no such column: session_id

Confirmed on this branch:

Scenario A: legacy DB missing all 4 additive columns      ✅
Scenario B: fresh DB                                       ✅
Scenario C: idempotency on already-migrated DB             ✅
Scenario D: partially-migrated DB (tenant+idempotency)     ✅

Test suite:

$ bash scripts/run_tests.sh tests/hermes_cli/test_kanban_db.py tests/hermes_cli/test_kanban_db_init.py
============================= 159 passed in 5.06s ==============================

Full tests/hermes_cli/ suite — 4888 passed, 13 pre-existing failures unrelated to kanban (gateway service / model picker / plugins) that match origin/main 1:1.

Lint diff: zero new ruff/ty issues vs origin/main (123 ty issues on changed files, identical on both base and head).

Credit

Original bug report and PR by @itsreverence (#28464 + #28461 — first to file, best write-up of the underlying ordering trap).

Cherry-picked commit on this branch authored by @quocanh261997 from #28754 (broadest open coverage — included tasks.tenant and tasks.idempotency_key).

Other PRs absorbed and to be closed as duplicates: #28741 (@stormhierta), #28562 (@kungunier), #28620 (@vanhci), #28602 (@verybigdog, DRAFT). All of them correctly diagnosed the bug; this salvage combines the broadest coverage with the most thorough comments and tests.

Closes #28464. Closes #28554. Closes #28617. Closes #28654. Closes #28698.

Changed files

  • hermes_cli/kanban_db.py (modified, +26/-18)
  • tests/hermes_cli/test_kanban_db.py (modified, +84/-1)

Code Example

sqlite3.OperationalError: no such column: session_id

---

CREATE TABLE IF NOT EXISTS tasks (... session_id TEXT ...);

---

CREATE INDEX IF NOT EXISTS idx_tasks_session_id ON tasks(session_id);

---

def test_connect_migrates_legacy_tasks_before_session_index(tmp_path):
    db_path = tmp_path / "legacy-kanban.db"
    conn = kb.sqlite3.connect(str(db_path))
    try:
        conn.executescript(
            """
            CREATE TABLE tasks (
                id TEXT PRIMARY KEY,
                title TEXT NOT NULL,
                body TEXT,
                assignee TEXT,
                status TEXT NOT NULL,
                priority INTEGER DEFAULT 0,
                created_by TEXT,
                created_at INTEGER NOT NULL,
                started_at INTEGER,
                completed_at INTEGER,
                workspace_kind TEXT NOT NULL DEFAULT 'scratch',
                workspace_path TEXT,
                claim_lock TEXT,
                claim_expires INTEGER
            );
            """
        )
    finally:
        conn.close()

    kb._INITIALIZED_PATHS.discard(str(db_path.resolve()))
    with kb.connect(db_path) as migrated:
        cols = {row["name"] for row in migrated.execute("PRAGMA table_info(tasks)")}
        indexes = {row["name"] for row in migrated.execute("PRAGMA index_list(tasks)")}

    assert "session_id" in cols
    assert "idx_tasks_session_id" in indexes

---

conn.executescript(SCHEMA_SQL)
_migrate_add_optional_columns(conn)
_ensure_indexes(conn)

---

tests/hermes_cli/test_kanban_db_init.py + tests/plugins/test_kanban_dashboard_plugin.py
96 passed
RAW_BUFFERClick to expand / collapse

Summary

A legacy kanban.db can fail dashboard/Kanban initialization with:

sqlite3.OperationalError: no such column: session_id

The issue appears when the existing tasks table predates the newer optional session_id column.

Root cause

hermes_cli/kanban_db.py::connect() runs conn.executescript(SCHEMA_SQL) before _migrate_add_optional_columns(conn).

For a legacy DB, this statement is a no-op because the table already exists:

CREATE TABLE IF NOT EXISTS tasks (... session_id TEXT ...);

But SCHEMA_SQL then immediately runs:

CREATE INDEX IF NOT EXISTS idx_tasks_session_id ON tasks(session_id);

Because _migrate_add_optional_columns() has not run yet, the legacy tasks table still has no session_id, so SQLite raises no such column: session_id before the migration gets a chance to add it.

Fresh DBs are fine; legacy DBs can fail.

Reproduction shape

  1. Create a SQLite DB with a legacy tasks table that has the older columns but no session_id.
  2. Call kanban_db.connect(db_path).
  3. Observe sqlite3.OperationalError: no such column: session_id from conn.executescript(SCHEMA_SQL).

Minimal regression-test shape:

def test_connect_migrates_legacy_tasks_before_session_index(tmp_path):
    db_path = tmp_path / "legacy-kanban.db"
    conn = kb.sqlite3.connect(str(db_path))
    try:
        conn.executescript(
            """
            CREATE TABLE tasks (
                id TEXT PRIMARY KEY,
                title TEXT NOT NULL,
                body TEXT,
                assignee TEXT,
                status TEXT NOT NULL,
                priority INTEGER DEFAULT 0,
                created_by TEXT,
                created_at INTEGER NOT NULL,
                started_at INTEGER,
                completed_at INTEGER,
                workspace_kind TEXT NOT NULL DEFAULT 'scratch',
                workspace_path TEXT,
                claim_lock TEXT,
                claim_expires INTEGER
            );
            """
        )
    finally:
        conn.close()

    kb._INITIALIZED_PATHS.discard(str(db_path.resolve()))
    with kb.connect(db_path) as migrated:
        cols = {row["name"] for row in migrated.execute("PRAGMA table_info(tasks)")}
        indexes = {row["name"] for row in migrated.execute("PRAGMA index_list(tasks)")}

    assert "session_id" in cols
    assert "idx_tasks_session_id" in indexes

Proposed fix

Create indexes only after additive migrations have run:

  1. Keep table creation in SCHEMA_SQL.
  2. Move index DDL into a separate list/helper, e.g. REQUIRED_INDEX_SQL + _ensure_indexes(conn).
  3. In connect(), call:
conn.executescript(SCHEMA_SQL)
_migrate_add_optional_columns(conn)
_ensure_indexes(conn)

This keeps fresh DB behavior unchanged while allowing legacy DBs to add optional columns before any index references them.

Local verification

I tested this approach locally against current main base 070eeaae6:

tests/hermes_cli/test_kanban_db_init.py + tests/plugins/test_kanban_dashboard_plugin.py
96 passed

Dashboard API smoke also passed for:

  • GET /api/plugins/kanban/boards
  • GET /api/plugins/kanban/board?board=default
  • POST /api/plugins/kanban/tasks?board=default
  • GET /api/plugins/kanban/tasks/{id}?board=default
  • DELETE /api/plugins/kanban/tasks/{id}?board=default

Filed by Dhananjay Jagtap (mrdjaycreations) while integrating Hermes into a local Nexus/Brand Wisdom single-user setup.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix Kanban legacy DB migration fails creating session_id index before column exists [1 pull requests, 1 comments, 2 participants]