Rename all references from storkit to huskies across the codebase:
- .storkit/ directory → .huskies/
- Binary name, Cargo package name, Docker image references
- Server code, frontend code, config files, scripts
- Fix script/test to build frontend before cargo clippy/test
so merge worktrees have frontend/dist available for RustEmbed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The timer tick loop now calls move_story_to_current() before
start_agent(), so stories scheduled from the backlog are moved into the
pipeline automatically when the timer fires. The timer bot command also
accepts backlog stories (previously required current).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
strip_mention_separator now skips all non-ASCII-alphanumeric chars
(emoji, colons, spaces) and returns a slice starting at the first
command character. Fixes mention pills with emoji display names
(e.g. "timmy ⚡️ status") not matching bot commands.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The /help test expected the help overlay to appear, but /help now goes
through botCommand like other slash commands. Updated the test to match.
Also added reader thread join and child.wait() calls to
claude_code.rs to prevent PTY master fd leaks from web UI chat sessions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The reader thread spawned in run_agent_pty_blocking was never joined,
leaving a cloned PTY master fd open after the agent exited. When the
pipeline restarted the agent on the same worktree, the stale fd from
the previous session interfered with the new PTY allocation, causing
Claude Code's bundled ripgrep to crash with:
fatal runtime error: assertion failed: output.write(&bytes).is_ok()
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Builds aarch64-unknown-linux-musl via cross alongside the existing
x86_64 Linux and macOS arm64 targets.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The acceptance gate was hardcoded to run cargo clippy, which fails on
non-Rust projects (Go, Node, etc.). Now the gate only runs script/test
which is project-specific. Clippy is added to storkit's own script/test
so Rust linting is preserved for this project.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The LLM was having the conversation with the user but never following
through with wizard_generate calls. The instructions now spell out
the full workflow: get hint, write content, stage it, show user, confirm.
Also adds "keep moving" instruction so the LLM auto-advances to the
next step after confirmation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously unblock only checked for blocked=true. Stories stuck in
merge with a merge_failure field were not considered "blocked" and
unblock refused to act. Now it clears both blocked and merge_failure,
and reports which fields were cleared.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There were two places checking for code changes: the post-cherry-pick
verification (already fixed) and a pre-cherry-pick check in the
merge-queue worktree. The pre-cherry-pick check was still filtering
all of .storkit/ which rejected stories that only change project.toml.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
wizard_generate now checks if the project has no source code. On bare
projects, the generation hints tell the LLM to ask the user what they
want to build and what tech stack they plan to use, rather than trying
to read a nonexistent codebase.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The format_wizard_state hints now tell the LLM what to do ("show it
to the user and ask if they're happy") rather than exposing tool names
to the user ("Run wizard_generate").
README wizard instructions now distinguish between existing-code projects
(read codebase, generate files) and bare projects (interview the user
about what they want to build).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The wizard check was only in CLAUDE.md which is Claude-specific.
Move the primary instruction to .storkit/README.md (step 1 of First
Steps) so any LLM reading the dev process docs will discover the wizard.
CLAUDE.md keeps a shorter pointer to the README.
Also fix stale .story_kit/ paths to .storkit/ in the README.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Change from passive "call wizard_status to check progress" to active
"On your first conversation, call wizard_status" with IMPORTANT prefix.
Without the direct instruction, Claude ignores the wizard tools.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Without this, Claude Code in a freshly scaffolded project has no idea
storkit's wizard or MCP tools exist and gives generic setup advice.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The post-cherry-pick diff check was excluding all of .storkit/, which
rejected stories whose deliverable is .storkit/project.toml changes
(e.g. 431 updating QA agent prompts). Narrow the exclusion to
.storkit/work/ which is where pipeline file moves live.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Manual merge of story 399 feature branch, adapted for the current CLI
parser (which includes the init subcommand from 429).
- storkit --port 3000 sets the listening port
- storkit --port=3000 also works
- Port resolution: CLI flag > STORKIT_PORT env > default 3001
- Supports combining with init: storkit init --port 3000 /path
- Replaces CliDirective enum with CliArgs struct that handles both
--port and init in a single pass
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Skip the version bump commit if nothing changed, so re-running
script/release for the same version doesn't fail on empty commit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The changelog grep commands return exit code 1 when no commits match,
which set -euo pipefail treats as fatal. Add || true guards so the
script continues to the tag/push/release steps.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After the cherry-pick step in run_squash_merge, verify:
1. project_root is on the base branch (not a merge-queue branch)
2. HEAD commit has actual code changes (not an empty/story-only diff)
If either check fails, return success=false so the story stays in merge
stage for retry instead of being phantom-advanced to done.
Also rename move_story_to_archived → move_story_to_done.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Story 424's merge used the wrong variant name HardBlock instead of
RateLimitHardBlock, breaking master compilation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The initial commit added the `throttled` field to `StoryAgent` but missed
several construction sites in lifecycle.rs, test_helpers.rs, and scan.rs.
Also adds the `HardBlock` match arm in the WebSocket event conversion and
minor CSS/import ordering fixes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add HardBlock variant to WatcherEvent (story_id, agent_name, reset_time)
- In pty.rs, distinguish allowed_warning (throttle) from hard blocks;
emit RateLimitWarning for throttles, HardBlock for actual 429s
- Add `throttled: bool` field to StoryAgent / AgentInfo
- Pool spawns a background listener that sets throttled=true on
RateLimitWarning or HardBlock events and fires AgentStateChanged
- Status command shows traffic-light dots: ○ idle, ● running, ◑ throttled, ✗ blocked
- Read blocked flag from story front matter for the ✗ dot
- Notifications: RateLimitWarning silenced (too noisy); HardBlock sends
urgent chat notification with optional reset time
- Tests added for traffic_light_dot, read_story_blocked, status output,
and all notification paths
The new WatcherEvent::RateLimitHardBlock variant added in the feature
commit was not covered in the ws.rs From<WatcherEvent> match, causing
a compile error. Add the missing arm returning None (same as
RateLimitWarning — handled by chat notifications only, not WebSocket).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add `unblock` bot command (chat + web UI slash command) that clears the
`blocked` flag and resets `retry_count` to 0 in story front matter
- Works across all pipeline stages (1_backlog through 6_archived)
- Returns confirmation with story name and ID, or clear error if story
is not found or not blocked
- Expose `unblock_story` MCP tool for programmatic use by agents
- Make `chat::commands::unblock` module pub(crate) so story_tools can
call `unblock_by_number`
- Add 8 unit tests covering registration, validation, core logic, and
edge cases (not-found, not-blocked, any stage, story ID in response)
- Update MCP tools list test: 49 → 50 tools
Add three HTTP endpoints for OAuth login without terminal access:
- GET /oauth/authorize — generates PKCE params, redirects to
claude.com/cai/oauth/authorize with code=true and full scopes
- GET /callback — exchanges auth code for tokens via JSON POST to
platform.claude.com/v1/oauth/token, writes ~/.claude/.credentials.json
- GET /oauth/status — returns current credential state as JSON
Uses SHA-256 (sha2 crate) for PKCE code challenge. The authorize URL
targets claude.com/cai/ (not platform.claude.com) which is required
for Max/Pro subscriptions to grant user:inference scope.
Users visit http://localhost:3001/oauth/authorize in their browser
to authenticate. Matrix/WhatsApp can send this link when auth fails.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Detect authentication_failed errors from the Claude Code PTY stream
and automatically refresh the OAuth access token using the stored
refresh token in ~/.claude/.credentials.json.
- New module server/src/llm/oauth.rs: reads credentials, calls
platform.claude.com/v1/oauth/token with JSON body, writes back
- PTY provider detects "error":"authentication_failed" via AtomicBool
- chat_stream retries once after successful refresh
- Clear error message if refresh also fails
On success the retry is transparent. On failure the user sees:
"OAuth session expired. Please run claude login to re-authenticate."
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Archive completed bug 397
- Create story 399 (CLI port flag)
- Remove @biomejs/cli-darwin-arm64 and @rollup/rollup-darwin-arm64
from package.json (breaks Docker builds on Linux)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New projects now get bot.toml.matrix.example, bot.toml.whatsapp-meta.example,
bot.toml.whatsapp-twilio.example, and bot.toml.slack.example in .storkit/
during scaffolding.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the monolithic bot.toml.example with separate files for each
transport: matrix, whatsapp-meta, whatsapp-twilio, and slack. Add a
chat bot configuration section to the README explaining that only one
transport can be active at a time and how to set up each one.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. **Check for MCP Tools:** Read `.mcp.json` to discover the MCP server endpoint. Then list available tools by calling:
1. **Check Setup Wizard:** Call `wizard_status` to check if project setup is complete. If the wizard is not complete, guide the user through the remaining steps. Important rules for the wizard flow:
- **Be conversational.** Don't show tool names, step numbers, or raw wizard output to the user.
- **On projects with existing code:** Read the codebase and generate each file, then show the user what you wrote and ask if it looks right.
- **On bare projects with no code:** Ask the user what they want to build, what language/framework they plan to use, and generate files from their answers.
- **You must actually generate the files.** The workflow for each step is: (1) call `wizard_generate` with no args to get a hint, (2) write the file content yourself based on the conversation, (3) call `wizard_generate` again with the `content` argument containing the full file body, (4) show the user what you wrote, (5) call `wizard_confirm` (they approve), `wizard_retry` (they want changes), or `wizard_skip` (they want to skip). Do not stop after discussing — follow through and write the files.
- **Keep moving.** After each step is confirmed, immediately proceed to the next wizard step without waiting for the user to ask.
2. **Check for MCP Tools:** Read `.mcp.json` to discover the MCP server endpoint. Then list available tools by calling:
This returns the full tool catalog (create stories, spawn agents, record tests, manage worktrees, etc.). Familiarize yourself with the available tools before proceeding. These tools allow you to directly manipulate the workflow and spawn subsidiary agents without manual file manipulation.
2. **Read Context:** Check `.story_kit/specs/00_CONTEXT.md` for high-level project goals.
3. **Read Stack:** Check `.story_kit/specs/tech/STACK.md` for technical constraints and patterns.
4. **Check Work Items:** Look at `.story_kit/work/1_backlog/` and `.story_kit/work/2_current/` to see what work is pending.
3. **Read Context:** Check `.huskies/specs/00_CONTEXT.md` for high-level project goals.
4. **Read Stack:** Check `.huskies/specs/tech/STACK.md` for technical constraints and patterns.
5. **Check Work Items:** Look at `.huskies/work/1_backlog/` and `.huskies/work/2_current/` to see what work is pending.
---
@@ -228,7 +234,29 @@ If a user hands you this document and says "Apply this process to my project":
---
## 6. Code Quality
## 6. Chat Bot Configuration
Story Kit includes a chat bot that can be connected to one messaging platform at a time. The bot handles commands, LLM conversations, and pipeline notifications.
**Only one transport can be active at a time.** To configure the bot, copy the appropriate example file to `.huskies/bot.toml`:
The `bot.toml` file is gitignored (it contains secrets). The example files are checked in for reference.
---
## 7. Code Quality
**MANDATORY:** Before completing Step 3 (Verification) of any story, you MUST run all applicable linters, formatters, and test suites and fix ALL errors and warnings. Zero tolerance for warnings or errors.
system_prompt="You are a QA agent. Your job is read-only: review code quality, run tests, try to start the server, and produce a structured QA report. Do not modify code. The server automatically runs acceptance gates when your process exits."
system_prompt="You are a QA agent. Your job is read-only: run quality gates, verify each acceptance criterion against the diff, and produce a structured QA report. Always call approve_qa or reject_qa via MCP to record your verdict. Do not modify code."
[[agent]]
name="coder-opus"
@@ -135,40 +173,62 @@ system_prompt = "You are a senior full-stack engineer working autonomously in a
[[agent]]
name="qa"
stage="qa"
role="Reviews coder work in worktrees: runs quality gates, generates testing plans, and reports findings."
role="Reviews coder work in worktrees: runs quality gates, verifies acceptance criteria, and reports findings."
system_prompt="You are a QA agent. Your job is read-only: review code quality, run tests, try to start the server, and produce a structured QA report. Do not modify code. The server automatically runs acceptance gates when your process exits."
system_prompt="You are a QA agent. Your job is read-only: run quality gates, verify each acceptance criterion against the diff, and produce a structured QA report. Always call approve_qa or reject_qa via MCP to record your verdict. Do not modify code."
@@ -118,8 +118,8 @@ To support both Remote and Local models, the system implements a `ModelProvider`
Multiple instances can run simultaneously in different worktrees. To avoid port conflicts:
- **Backend:** Set `STORKIT_PORT` to a unique port (default is 3001). Example: `STORKIT_PORT=3002 cargo run`
- **Frontend:** Run `npm run dev` from `frontend/`. It auto-selects the next unused port. It reads `STORKIT_PORT` to know which backend to talk to, so export it before running: `export STORKIT_PORT=3002 && cd frontend && npm run dev`
- **Backend:** Set `HUSKIES_PORT` to a unique port (default is 3001). Example: `HUSKIES_PORT=3002 cargo run`
- **Frontend:** Run `npm run dev` from `frontend/`. It auto-selects the next unused port. It reads `HUSKIES_PORT` to know which backend to talk to, so export it before running: `export HUSKIES_PORT=3002 && cd frontend && npm run dev`
When running in a worktree, use a port that won't conflict with the main instance (3001). Ports 3002+ are good choices.
# Story 388: WhatsApp webhook HMAC signature verification
## User Story
As a bot operator, I want incoming WhatsApp webhook requests to be cryptographically verified, so that forged requests from unauthorized sources are rejected.
## Acceptance Criteria
- [ ] Meta webhooks: validate X-Hub-Signature-256 HMAC-SHA256 header using the app secret before processing
- [ ] Twilio webhooks: validate request signature using the auth token before processing
- [ ] Requests with missing or invalid signatures are rejected with 403 Forbidden
- [ ] Verification is fail-closed: if signature checking is configured, unsigned requests are rejected
- [ ] Existing bot.toml config is extended with any needed secrets (e.g. Meta app_secret for HMAC verification)
- [ ] MUST use audited crypto crates (hmac, sha2, sha1, base64) — no hand-rolled cryptographic primitives
name: "Fly.io Machines API integration for multi-tenant huskies SaaS"
---
# Spike 408: Fly.io Machines API integration for multi-tenant huskies SaaS
## Question
Can we build a working Rust integration that creates and manages per-tenant Fly.io Machines, attaches volumes, injects Claude credentials, and proxies JWT-authenticated HTTP/WebSocket traffic to the right machine?
## Hypothesis
A thin Rust service using `reqwest` for the Machines API and `axum` for the reverse proxy is sufficient. No heavyweight orchestration framework needed.
## Prerequisites
- Fly.io account with API token (set `FLY_API_TOKEN` env var)
- Spike 407 findings reviewed
## Timebox
4 hours
## Investigation Plan
- [ ] Create a minimal Rust crate in `spikes/fly_machines/` — do not touch production code
- [ ] Implement machine lifecycle: create, start, stop, destroy via Fly Machines REST API using `reqwest`
- [ ] Test attaching a persistent volume to a machine and verify it persists across stop/start
- [ ] Test secret injection — pass a dummy `credentials.json` as a Fly secret and verify it's readable inside the machine
- [ ] Sketch the auth proxy: JWT validation → machine lookup → reverse proxy to machine's private IP; verify WebSocket proxying works
- [ ] Measure actual cold start time for a minimal huskies container image
- [ ] Document any API quirks, rate limits, or sharp edges discovered during testing
name: "Multi-account OAuth token rotation on rate limit"
---
# Story 411: Multi-account OAuth token rotation on rate limit
## User Story
As a huskies user with multiple Claude Max subscriptions, I want the system to automatically rotate to a different account when one gets rate limited, so that agents and chat don't stall out waiting for limits to reset.
## Acceptance Criteria
- [ ] OAuth login flow stores credentials per-account (keyed by email), not overwriting previous accounts
- [ ] GET /oauth/status returns all stored accounts and their status (active, rate-limited, expired)
- [ ] When the active account hits a rate limit, huskies automatically swaps to the next available account's refresh token, refreshes, and retries
- [ ] The bot sends a notification in Matrix/WhatsApp when it swaps accounts
- [ ] If all accounts are rate limited, the bot surfaces a clear message with the time until the earliest reset
- [ ] A new /oauth/authorize login adds to the account pool rather than replacing the current credentials
name: "Recheck bot command to re-run gates without restarting agent"
---
# Story 412: Recheck bot command to re-run gates without restarting agent
## User Story
As a user, I want to send `recheck <number>` to the bot so that it re-runs acceptance gates on an existing worktree without spawning a new agent, so I can unblock stories that failed due to environment issues without wasting agent turns.
## Acceptance Criteria
- [ ] recheck command is registered in chat/commands/mod.rs and appears in help output
- [ ]`recheck <number>` runs run_acceptance_gates on the story's existing worktree
- [ ] If gates pass, the story advances through the pipeline (same as if a coder completed successfully)
- [ ] If gates fail, the error output is returned to the user (not silently retried)
- [ ] If no worktree exists for the story, returns a clear error
- [ ] Does not spawn a new agent or increment retry_count
- [ ] Works from all transports (Matrix, WhatsApp, Slack)
name: "Unblock command handles all stuck states not just blocked flag"
---
# Story 435: Unblock command handles all stuck states not just blocked flag
## User Story
As a project owner, I want the unblock command to clear any stuck state on a story — not just the blocked flag — so that I have a single command to unstick stories regardless of why they're stuck.
## Acceptance Criteria
- [ ] Unblock clears merge_failure field in addition to blocked flag
- [ ] Unblock clears review_hold field
- [ ] Unblock reports which fields were cleared in the confirmation message
- [ ] Unblock works on stories in any pipeline stage (backlog, current, qa, merge, done)
- [ ] If no stuck state is found (no blocked, merge_failure, or review_hold), returns a clear message saying so
name: "Unify story stuck states into a single status field"
---
# Refactor 436: Unify story stuck states into a single status field
## Current State
- TBD
## Desired State
Replace the separate blocked, merge_failure, and review_hold front matter fields with a single status field (e.g. status: blocked, status: merge_failure, status: review_hold). Simplifies the unblock command, auto-assign checks, and pipeline advance logic.
## Acceptance Criteria
- [ ] Replace blocked: true, merge_failure: string, and review_hold: true with a single status: field in story front matter
- [ ] Auto-assign checks a single field instead of three separate ones
- [ ] Pipeline advance and lifecycle code reads/writes the unified status field
- [ ] Unblock command clears the status field regardless of which stuck state it was
- [ ] retry_count remains a separate field (it's a counter, not a state)
- [ ] Migration: existing stories with old fields are handled gracefully on read
name: "Rename project from \"huskies\" to \"huskies\""
---
# Story 455: Rename project from "huskies" to "huskies"
## User Story
As a project maintainer, I want to rename the project from \"huskies\" to \"huskies\" so that the product has its new identity throughout the codebase, tooling, and documentation. The new domain is huskies.dev — update all references to huskies.dev accordingly (website, contact email hello@huskies.dev, etc).
## Acceptance Criteria
- [ ] Rust crate name in server/Cargo.toml changed from 'huskies' to 'huskies'
- [ ] Binary name changed to 'huskies' (Dockerfile CMD, release script binary names)
- [ ] Docker service name, container_name, image name, and volume names updated in docker-compose.yml
- [ ] Docker user/group renamed from 'huskies' to 'huskies' in Dockerfile (groupadd, useradd, home dir /home/huskies/.claude)
- [ ] MCP server registration renamed from 'huskies' to 'huskies' in scaffold-generated .mcp.json and in server/src/http/mcp/mod.rs serverInfo name
- [ ] All 35+ MCP tool permission patterns updated from mcp__huskies__* to mcp__huskies__* across code and permission configs
- [ ] The .huskies/ project directory marker renamed to .huskies/ throughout all Rust source (paths.rs, config.rs, scaffold.rs, watcher.rs, prompts.rs, and all agent/pipeline code)
- [ ] Release script updated: Gitea repo path dave/huskies → dave/huskies, changelog regex updated to match ^(huskies|huskies|story-kit): for backwards-compatible history parsing, binary artifact names updated
- [ ] Git commit prefix convention updated from 'huskies:' to 'huskies:' in huskies README and agent prompts
- [ ] Website updated: page title, headings, and contact email (hello@huskies.dev) if domain changes
- [ ] README.md updated: all CLI examples use 'huskies' binary name, all .huskies/ references become .huskies/
- [ ] A migration path exists for existing installs: either huskies auto-detects and migrates .huskies/ → .huskies/, or a migration script (script/migrate) is provided
- [ ] All Claude Code .mcp.json files in existing worktrees are regenerated via scaffold or migration
- [ ] Gitea repository renamed from dave/huskies to dave/huskies (external action required, noted in story)
name: "Stage transition notifications can arrive out of order and show wrong story name"
agent: coder-opus
---
# Bug 462: Stage transition notifications can arrive out of order and show wrong story name
## Description
When a story moves through stages quickly (e.g. QA → Merge → Done), the stage transition notifications can arrive out of order in Matrix chat. The Done notification appears before the Merge notification.
Two issues:
1.**Out-of-order delivery**: When two notifications are sent close together, the Matrix homeserver can deliver them in the wrong order. The notification handler processes events sequentially and awaits each send, but the homeserver does not guarantee ordering for near-simultaneous messages.
2.**Missing story name on stale notifications**: The second notification shows the raw item_id instead of the story name because `read_story_name` looks in the stage directory from the event (e.g. `4_merge/`), but the file has already moved to the next stage (e.g. `5_done/`).
3.**`inferred_from_stage` guesses the source stage** instead of tracking the actual from-stage. This means skipped stages would show incorrect transitions.
## How to Reproduce
1. Have a story in QA that passes quickly
2. Story moves QA → Merge → Done in rapid succession
3. Observe notifications in Matrix
## Actual Result
Notifications arrive in wrong order (Done before Merge). The later notification shows the raw item_id instead of the story name:
Notifications arrive in chronological order. All notifications show the story name. Ideally, rapid transitions are coalesced into a single notification for the final stage.
## Acceptance Criteria
- [ ] read_story_name falls back to searching all stages when the expected stage directory has no match
- [ ] Consider deduplicating rapid transitions within a short window (e.g. only notify for the final stage)
- [ ] Track actual from-stage in WatcherEvent instead of guessing via inferred_from_stage
name: "Timer rejects backlog stories — should move to current on fire"
---
# Bug 464: Timer rejects backlog stories — should move to current on fire
## Description
The `timer` bot command requires stories to be in `work/2_current/` before scheduling. When a user tries to schedule a backlog story (e.g. `timer 463 12:45`), it returns:
"Story **463_story_...** is not in `work/2_current/`. Move it to current before scheduling a timer."
The timer should accept backlog stories. When the timer fires, it should move the story from backlog to current and let auto-assign start an agent.
## How to Reproduce
1. Have a story in backlog (e.g. 463)
2. Run `timer 463 12:45`
3. Observe rejection message
## Actual Result
Timer command rejects stories not in `work/2_current/`.
## Expected Result
Timer command accepts backlog stories. When the timer fires, it moves the story to current and auto-assign picks it up.
## Acceptance Criteria
- [ ] Timer bot command accepts stories in backlog or current
- [ ] Timer tick loop calls move_story_to_current before start_agent for backlog stories
- [ ] Unit tests cover scheduling and firing for backlog stories
# 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
name: "Configurable timezone in project.toml for timer scheduling"
---
# Story 466: Configurable timezone in project.toml for timer scheduling
## User Story
As a user running huskies in a container where TZ defaults to UTC, I want to configure my project's timezone in project.toml so that timer HH:MM inputs are interpreted in my actual timezone.
## Acceptance Criteria
- [ ] Add a `timezone` field to project.toml (e.g. `timezone = "Europe/London"`)
- [ ] next_occurrence_of_hhmm uses the configured timezone instead of chrono::Local
- [ ] Falls back to chrono::Local if no timezone is configured
- [ ] Timer confirmation message displays the time in the configured timezone
- [ ] timer list command shows times in the configured timezone
@@ -10,7 +10,7 @@ The `prompt_permission` MCP tool returns plain text ("Permission granted for '..
## How to Reproduce
1. Start the storkit server and open the web UI
1. Start the huskies server and open the web UI
2. Chat with the claude-code-pty model
3. Ask it to do something that requires a tool NOT in `.claude/settings.json` allow list (e.g. `wc -l /etc/hosts`, or WebFetch to a non-allowed domain)
@@ -6,7 +6,7 @@ name: "Retry limit for mergemaster and pipeline restarts"
## User Story
As a developer using storkit, I want pipeline auto-restarts to have a configurable retry limit so that failing agents don't loop infinitely consuming CPU and API credits.
As a developer using huskies, I want pipeline auto-restarts to have a configurable retry limit so that failing agents don't loop infinitely consuming CPU and API credits.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.