---
name: "Timer tick loop never fires due entries"
agent: coder-opus
---

# Bug 465: Timer tick loop never fires due entries

## Description

The timer tick loop (`spawn_timer_tick_loop`) is spawned by the Matrix bot runner, the Matrix bot is confirmed running (processing messages), but timers never fire. Past-due entries remain in `.huskies/timers.json` indefinitely — `take_due` never consumes them.

The tick loop uses `tokio::spawn` which swallows panics silently. If `move_story_to_current` or `start_agent` panics on the first tick (when all past-due entries fire at once), the entire task dies with no log output. The PTY debug spam may also push any `[timer]` log entries out of the ring buffer.

The bot command successfully adds entries to the in-memory store and persists them to disk, but the tick loop never processes them.

## How to Reproduce

1. Set a timer via bot command: `timer 463 HH:MM` (a time in the near future)
2. Wait past the scheduled time
3. Check `.huskies/timers.json` — entries are still present
4. Check server logs for `[timer]` — no entries found

## Actual Result

Timer entries remain in timers.json indefinitely. No `[timer] Timer fired` log entries appear. The story is never moved to current and no agent is started.

## Expected Result

Within 30 seconds of the scheduled time, the tick loop should call `take_due`, remove the entry from disk, move the story to current (if in backlog), and start an agent.

## Acceptance Criteria

- [ ] Add panic-catching (catch_unwind or tokio CancellationToken) to the tick loop so failures are logged
- [ ] Add a startup log line confirming the tick loop is running and how many pending timers were loaded
- [ ] Verify take_due runs on each 30-second tick by adding periodic debug logging
- [ ] Integration test: create a past-due timer entry, run the tick loop, assert the entry is consumed
