timebomb
Scan source code for deadline-tagged fuses and fail when they detonate.
The problem it solves: // TODO: remove this after the migration gets written with good intentions and stays forever. timebomb makes the deadline explicit and machine-enforceable. When the date passes, the build fails — forcing a fix or a conscious decision to extend the deadline.
Fuse format
// TODO[2026-06-01]: remove this feature flag once the experiment ends
# FIXME[2026-03-15][alice]: workaround for upstream bug, revert after upgrade
-- HACK[2025-12-31]: temporary shim, drop this column after migration
Syntax: TAG[YYYY-MM-DD]: message or TAG[YYYY-MM-DD][owner]: message
The tag must be immediately followed by [date] with no space. Plain // TODO: fix this comments (no bracket-date) are ignored entirely, so you can adopt timebomb incrementally without touching existing annotations.
The scanner is language-agnostic — it matches the pattern anywhere on a line regardless of comment syntax (//, #, --, ;;, %, *, anything). No language-specific parsers.
Default triggers
TODO, FIXME, HACK, TEMP, REMOVEME, DEBT, STOPSHIP, WORKAROUND, DEPRECATED, BUG
Tags are matched case-insensitively. The full set is configurable via .timebomb.toml.
Fuse status
Each fuse is classified relative to the current date, which is derived once at startup and threaded through the entire scan (so long runs across midnight are consistent):
| Status | Condition |
|---|---|
detonated |
Date is in the past |
ticking |
Date is within the fuse_days warning window |
inert |
Date is beyond the warning window |
Installation
Pre-built binaries (fastest)
Download the latest release binary for your platform from GitHub Releases:
# Linux x86_64
&&
# macOS Apple Silicon
&&
# macOS Intel
&&
# Windows x86_64 (PowerShell)
Via cargo
Via Docker
The image is built from Docker Hardened Images: a DHI Rust builder and a distroless DHI static runtime.
From source
Build and push the container image
Defaults push pwbsladek/timebomb:<Cargo.toml version> and pwbsladek/timebomb:latest. Override with IMAGE, IMAGE_TAG, or PLATFORMS:
GitHub Actions also pushes the Docker image on release when release-please creates a release tag, using DOCKERHUB_USERNAME and DOCKERHUB_TOKEN repository secrets.
Commands
sweep — scan and detonate in CI
sweep is the only command that exits non-zero. All other commands are informational and always exit 0.
armory — prioritize active fuses
armory ranks detonated fuses first, with the most overdue at the top, then ticking fuses by soonest deadline. It always exits 0.
explain — focus an agent on one fuse
explain scans the project, finds the fuse at FILE:LINE, and prints the annotation with a short remediation menu. It exits 0 when a fuse is found and exits 2 if the target line has no fuse.
manifest — list all fuses
Terminal output includes a compact age column showing days until expiry or overdue:
DETONATED src/auth/login.rs:42 TODO[2025-01-15] -433d [alice] remove legacy oauth flow
TICKING src/db/schema.sql:108 FIXME[2026-04-08] +15d drop temp_users table
INERT src/api/handler.rs:77 HACK[2099-01-01] +26946d revisit when platform ships
defuse — interactively resolve detonated fuses
For each detonated fuse, defuse prompts:
DETONATED src/auth/login.rs:42 TODO[2025-01-15]: remove legacy oauth flow
[e] Extend to new date
[d] Delete line
[s] Skip
Choice:
Extend prompts for a new date and rewrites the annotation in-place. Delete removes the line. Files are updated in a single bottom-up pass per file to avoid line-shift bugs.
plant — insert a new fuse
delay — bump a deadline
disarm — remove a fuse
intel — breakdown by owner, tag, or month
tripwire — manage the git pre-commit hook
The hook block written by tripwire set:
# BEGIN timebomb
# END timebomb
Installing twice is idempotent. Cutting removes only the marked block; if the file becomes empty it is deleted.
fallout — compare two report snapshots
Reads two JSON reports produced by timebomb sweep --format json and shows how fuse debt changed between them — newly detonated, resolved, and delayed (deadline bumped without fixing).
bunker — ratchet enforcement
bunker save writes .timebomb-baseline.json:
When this file exists, timebomb sweep automatically loads it and exits 1 if the current detonated or ticking count exceeds the baseline — preventing debt from growing while not requiring everything to be fixed at once.
Hard ceilings can also be set in .timebomb.toml independently of the baseline file:
= 0
= 5
completions — shell completion scripts
Pipe to your completions directory to enable tab-completion for all subcommands and flags:
# zsh
# bash (user-level, no sudo required)
# bash (system-wide, requires sudo)
|
# fish
Output formats
Terminal (default)
DETONATED src/auth/login.rs:42 TODO[2026-01-15] -433d remove legacy oauth flow
TICKING src/db/schema.sql:108 FIXME[2026-04-01] +8d drop temp_users table
INERT src/api/handler.rs:77 HACK[2099-01-01] +26946d revisit when platform ships
Swept 142 file(s) · 17 fuse(s) · 1 detonated · 1 ticking · 15 inert
The age column (-Xd / +Xd) shows how many days overdue or until expiry. With --blame, unowned fuses show the git blame author as [~name]. Explicit [owner] brackets are shown as-is and are never overwritten.
Respects NO_COLOR.
JSON (--format json)
CSV (--format csv, manifest only)
file,line,tag,date,owner,status,message
src/auth/login.rs,42,TODO,2026-01-15,,detonated,remove legacy oauth flow
Fields containing commas or quotes are quoted per RFC 4180.
Agent summary (sweep --agent-summary)
timebomb: failed
swept_files: 142
total_fuses: 17
detonated: 2
ticking: 1
inert: 14
next_action:
- fix src/auth/login.rs:42 TODO[2025-01-15][alice]: remove legacy oauth flow
This format is intentionally compact and deterministic so agents can paste it into task reports or PR comments.
Fix plan (sweep --fix-plan json)
The fix plan is non-mutating. It only includes detonated and ticking fuses.
GitHub Actions (--format github)
Emits workflow commands that appear as inline PR annotations:
::error file=src/auth/login.rs,line=42::TODO detonated on 2026-01-15: remove legacy oauth flow
::warning file=src/db/schema.sql,line=108::FIXME ticking until 2026-04-01: drop temp_users table
Auto-detected when GITHUB_ACTIONS=true is set.
Configuration
.timebomb.toml in the project root:
# Tags to scan for
= ["TODO", "FIXME", "HACK", "TEMP", "REMOVEME", "DEBT", "STOPSHIP", "WORKAROUND", "DEPRECATED", "BUG"]
# Flag fuses expiring within this many days as ticking (0 = disabled)
= 14
# Glob patterns to exclude from scanning
= [
"vendor/**",
"node_modules/**",
"*.min.js",
".git/**",
]
# File extensions to scan. If empty, all non-binary files are scanned.
= ["rs", "go", "ts", "js", "py", "rb", "java", "sql", "tf", "yaml", "yml"]
# Ratchet ceilings: sweep fails if live count exceeds these values.
= 0
= 5
| Key | Type | Default | Description |
|---|---|---|---|
triggers |
[string] |
see above | Tags to match (case-insensitive) |
fuse_days |
integer | 0 |
Days before expiry to enter ticking status |
exclude |
[string] |
["vendor/**","node_modules/**","*.min.js",".git/**"] |
Glob exclusions |
extensions |
[string] |
see defaults | Extensions to scan; empty means all non-binary |
max_detonated |
integer | — | Hard ceiling; sweep exits 1 if exceeded |
max_ticking |
integer | — | Hard ceiling; sweep exits 1 if exceeded |
CLI flags override config file values. If no config file is found, built-in defaults apply silently.
Environment variables
| Variable | Description |
|---|---|
TIMEBOMB_FUSE_DAYS |
Default fuse warning window (e.g. 14 or 14d). Overridden by --fuse. |
NO_COLOR |
Disable terminal color output when set. |
GITHUB_ACTIONS |
When true, auto-selects GitHub Actions output format. |
CI integration
GitHub Actions
name: timebomb
on:
push:
pull_request:
schedule:
- cron: '0 9 * * *' # daily sweep even without a push
jobs:
timebomb:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install timebomb
run: |
curl -sSL https://github.com/pbsladek/timebomb/releases/latest/download/timebomb-linux-x86_64 \
-o /usr/local/bin/timebomb
chmod +x /usr/local/bin/timebomb
- run: timebomb sweep --fuse 14d --fail-on-ticking
--format github is inferred automatically from GITHUB_ACTIONS=true, so workflow command annotations appear in the PR diff without any extra flags.
Pre-commit hook
Or manually in .git/hooks/pre-commit:
#!/bin/sh
Releases
Releases are automated via release-please. Every merge to main is inspected for Conventional Commits:
| Commit type | Version bump |
|---|---|
fix: |
patch |
feat: |
minor |
feat!: or BREAKING CHANGE: footer |
major |
release-please opens a release PR that bumps Cargo.toml and drafts the changelog. Merging that PR creates the git tag and GitHub release automatically.
The crates.io package is timebomb-cli, but the release component stays timebomb so release tags remain vX.Y.Z and the installed executable remains timebomb.
Scanner behavior
- Walk: Recursive directory walk via
walkdir. Symlinks are not followed. - Exclusions: Paths matching any
excludeglob are skipped before opening files. - Extension filter: Only files whose extension matches the
extensionslist are scanned. An empty list disables the filter. - Binary detection: The first 8 KB of each candidate file is checked for null bytes (
\x00). Files containing any are skipped silently. - Parallel scan: After the serial walk phase collects candidates, files are scanned in parallel via
rayon. The compiled regex is shared across all worker threads. - Invalid dates: A fuse with an unparseable date (e.g.
TODO[2026-13-45]) emits a warning to stderr and is skipped; the scan continues. - Sort: Results are sorted by date ascending so the most urgent fuses appear first.
Exit codes
| Code | Meaning |
|---|---|
0 |
Clean — no detonated fuses (or counts within baseline/ceilings) |
1 |
Detonated fuses found, ticking threshold exceeded with --fail-on-ticking, or ratchet ceiling breached |
2 |
Configuration or runtime error |
Development
Requires Rust 1.80+.
License
MIT — see LICENSE for details.