# Changelog ## 0.2.0 — 2026-09-25 This release comes from running the board daily with Claude Code agents for four and a half months across several projects — hundreds of cards and agent sessions, and one autonomous Claude-implements / Codex-reviews pipeline. Almost every item below maps to something that went wrong there. ### Fixed - **Polling rules no longer fire every minute on the same card.** A `task_idle` rule (e.g. "blocked for 7 days → priority high + comment") re-applied itself on every tick for as long as the card stayed put; one database collected ~750k identical history rows in four months, card modals froze and `kanban_get` overflowed agent context. Polling rules now act once per stay in a column (`rule_firings`), `set_priority` to the current value is a no-op, and updates that change nothing write no history. Clean up an affected database with `maintenance dedupe-history`. - **Moves made over MCP trigger rules and webhooks.** `kanban_move` wrote straight to SQLite, so the rule engine only ever saw drag-drops in the UI — an agent approving a card never launched anything. The server now tails `task_history` (event feed) and dispatches every change, whoever made it: UI, REST, MCP agents, scripts importing the store, the rule engine itself. - **A card created directly in a column counts as arriving there** (`task_moved` with `from_status: null`). Cards an agent filed straight into Approved used to wait forever. - **`KANBAN_DB` is honoured** by the store (documented before, ignored in code), and **`KANBAN_ACTOR` by the MCP server**. - `POST /api/tasks` with malformed `links` returned 500 (`KeyError: 'type'`); it is a 400 with a readable message now. Links/blockers on unknown tasks are 404s, unknown blocker ids and dependency cycles are rejected. - Concurrent requests could hit `sqlite3.InterfaceError` on the shared connection (`GET /api/board`); all store access is serialised now. - Writes from two processes (the web server and an MCP server, or two MCP servers) could fail instantly with "database is locked": transactions started with a deferred `BEGIN`, read, then tried to write, and SQLite does not wait in that case. Write transactions now start with `BEGIN IMMEDIATE` and wait up to 15 s for the other writer. - The external blocker could not be cleared from the UI (empty field was sent as "leave unchanged"). `""` clears it in the UI, REST and MCP. - Reordering a card inside its column reset its "in column since" time and wrote a history row; drag-drop indexes now renumber the column instead of producing duplicate positions. - `run_command` processes were in the server's process group — restarting the kanban (or launchd) killed running agents — and without `log_file` their output went to `/dev/null`. They get their own session, a UTF-8 locale when launchd provides none, and a default log file. - The HTTP MCP endpoint used fastapi-mcp's deprecated `mount()`, which served SSE at `/mcp` although the docs said streamable HTTP. - The Cursor setup in the docs pointed at the wrong config file. ### Added - **Archive**: `archived_at` on tasks, `POST /api/tasks/{id}/archive`, an `archive` rule action, an Archive button in the card and "· N archived" in the top bar. Archived cards leave the board and keep their status. (Archiving by `move_to cancelled` made done work look abandoned; run `maintenance restore-archived` to fix old data.) - **Assignee** over REST (`POST /api/tasks/{id}/assign`), MCP (`kanban_assign`), rules (`assign` action) and editable in the card dialog. - **Rule engine**: `blockers_done` trigger (with optional `resolved_statuses`), `task_idle` in `hours`/`minutes`, rule-level `repeat_every` and `wip_limit` (polling rules), `project_id` lists, a top-level `"paused": true` kill switch (also `POST /api/automation/pause` and ⏸ in the top bar), a loop guard for reactive rules, and invalid rules are skipped instead of disabling the file. - **`run_command`**: one process per card and rule at a time, `max_concurrent` with a queue, `max_runs` (block the card instead of the N+1-th launch; a person moving it out of Blocked resets the count — rules and agents don't), exit code 75 = "try again later", not counted. Queued and running jobs are stored in the database: a server that stops leaves working agents alone and starts nothing new; the next one re-attaches to them (pid and process start time must match) before resuming the queue in order. Jobs whose rule isn't loaded are kept and listed, not dropped. Running and queued commands are listed in `/api/automation/status`, and cards show `▶ agent` while one runs. - **Agent launcher v2** (`examples/agent-launcher/launch-claude.sh`): preflight (card still approved, `claude` found and logged in), per-card lock, explicit `--mcp-config` + `--strict-mcp-config`, `--permission-mode dontAsk`, hard timeout that kills the whole process tree (and cleans up whatever the agent left running), optional model per card size, optional gate command, and a card blocked with the reason when the agent leaves it behind — only if nobody has moved it since. Usage limits are recognised from the CLI's error output, not from any "429" in token counts. Covered by tests with a fake `claude`. - **MCP**: `kanban_assign` and `kanban_history` (16 tools); `kanban_get` returns the newest 30 history rows plus `history_total`; mutating tools answer with a short card and a `url`; `kanban_move(expected_from=…)` refuses to act on a card someone else just moved; `KANBAN_PROJECT_ID` scopes list/search/board; `KANBAN_MCP_HUMAN_ONLY` (default `done`) — agents stop at `testing`. Arguments are strict: `project=`/`body=`/`column=` are errors that name the right parameter (they used to be dropped silently, creating empty cards); status names accept `"In progress"`, `"Testing"`, Russian column names. - **`python -m kanban_mcp.connect