Every backlog has that pile of tasks nobody gets to. Missing tests, stale docs, TODO comments from 2024, a small feature that's been "next sprint" for three months. Important enough to track, never urgent enough to do.
I wanted Claude Code to just... do them. While I sleep. The machine is on anyway -- why should Claude sit idle all night?
NightShift pulls tasks from your issue tracker (GitHub Issues, YouTrack, Trello, or just a YAML file), feeds them to Claude Code one by one, runs quality gates on the result, and opens draft PRs. You wake up, review the PRs over coffee, merge the good ones.
That's it. You sleep, robots work.
nightshift
NIGHTSHIFT next run in 5h 12m 4 pending 2 projects | last: 3β 1β
ββ TASK QUEUE βββββββββββββββββ¬β TASK DETAIL βββββββββββββββββββββββ
β ββ ACTIVE (2) ββββββββββββ β Title: Security audit β
β β [high] Security audit β Project: myapp β
β β [med] Resolve TODOs β Category: builtin β
β ββ BUILT-IN (1) ββββββββββ β Frequency:once β
β Β· [low] Update docs weekly β Priority: β high β
β ββ INACTIVE (1) ββββββββββ β Intent: Audit the codebase for β
β β [med] Write missing testsβ common security issues...β
ββ PROJECTS βββββββββββββββββββ€β RUN DETAIL ββββββββββββββββββββββββ
β myapp ~/Projects/myapp β Run 20260322 03:00 6m 47s β
β api ~/Projects/api β β Fix imports 2m 10s β
β β β Remove dead code 1m 17s β
β β β Add type hints 3m 20s β
β ββ RUN HISTORY ββββββββββββββββββββββββ
β β 20260322 3/22 03:00 2β 1β 6m β
β β 20260321 3/21 03:00 4β 0β 8m β
β β βββ
βββ
β pass rate β
βββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββ€
β [q] Quit [t] Add [x] Toggle/Remove [r] Run [s] Sync [m] Model β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- You label issues in GitHub/YouTrack/Trello with any tag you choose (or write tasks in YAML)
nightshift syncpulls them into a local queue- At 3 AM (or whenever you set it), NightShift wakes up and for each task:
- creates a branch
- runs baseline tests
- hands the task to Claude Code
- checks the result against quality gates (blast radius, linter, test regression)
- opens a draft PR if everything passes
- You review the PRs in the morning
Every change goes through quality gates before a PR is created. If Claude breaks tests or touches too many files -- the task fails, no PR is opened, you see the details in the dashboard.
# Requires Python 3.13+, uv recommended
uv tool install .
# Or for development
git clone https://github.com/androsovm/NightShift.git
cd NightShift
uv sync --extra dev- Claude Code CLI (
claude) - GitHub CLI (
gh) -- for creating PRs - Git with push access to your repositories
nightshift init # interactive setup wizard
nightshift doctor # verify environment
nightshift # open TUI dashboard
nightshift run --dry-run # preview what would happen
nightshift run # let it ripThe setup wizard walks you through 5 steps: pick your projects, configure task sources, set safety limits, add API tokens, choose a schedule. You can go back at any step.
Run nightshift with no arguments. Everything is a keystroke away:
| Key | Action |
|---|---|
t |
Add a built-in task (docs, tests, lint, etc.) |
x |
Toggle active/inactive for source tasks, remove built-in tasks |
m |
Change model for selected task |
p |
Cycle task priority (low β medium β high) |
r |
Run selected task |
R |
Run all pending tasks |
e |
Retry a failed task |
s |
Sync tasks from sources |
d |
Run doctor health check |
j/k |
Navigate lists |
Tab |
Switch panels |
? |
Help |
q |
Quit |
The task queue is split into three sections:
- Active β source-received and manual tasks in the execution queue
- Built-in β template-based tasks with a frequency: once, weekly, or monthly. Recurring tasks auto-requeue after their interval upon successful completion
- Inactive β tasks you've deferred with
[x]. They won't run until reactivated. Press[x]again to move them back to Active
Don't want to write tasks from scratch? Press [t] in the TUI -- pick a template, project, model, and frequency (once / weekly / monthly). Still a work in progress, more templates coming:
| Template | What it does |
|---|---|
docs |
Update README, docstrings, inline comments |
tests |
Write unit tests for uncovered code paths |
types |
Add type annotations to functions |
lint |
Fix all ruff/eslint/flake8 warnings |
todos |
Implement TODO/FIXME comments |
dead-code |
Remove unused imports, functions, files |
deps |
Update minor/patch dependency versions |
security |
Audit for OWASP top-10 vulnerabilities |
refactor |
Simplify functions >50 lines |
Each template comes with a pre-filled intent, scope, and constraints so Claude gets a clear, focused assignment.
schedule:
time: "03:00"
timezone: Europe/Berlin
max_duration_hours: 4
projects:
- path: /home/user/projects/myapp
sources: [yaml]
max_prs_per_night: 10sources:
- type: github
repo: user/myapp
labels: [nightshift]
limits:
max_tasks_per_run: 5
task_timeout_minutes: 45
max_files_changed: 20
max_lines_changed: 500
default_model: claude-sonnet-4-6
# Inline YAML tasks
tasks:
- id: remove-dead-code
title: Remove dead code in utils.py
intent: Find and remove unused functions
scope: [src/utils.py]
priority: mediumThe more context you give Claude, the better the result. Here's a task with all the fields:
tasks:
- id: add-health-endpoint
title: Add /healthz endpoint
priority: high
model: claude-sonnet-4-6
estimated_minutes: 20
intent: |
Add a GET /healthz endpoint that returns {"status": "ok", "version": "..."}
reading the version from pyproject.toml. Include a test that checks
the response status and JSON schema.
scope:
- src/api/routes.py
- tests/test_routes.py
constraints:
- Do not add new dependencies
- Follow the existing route registration pattern in routes.py
- Version must be read at import time, not on every request- intent -- what to do and why. Be specific.
- scope -- which files Claude should focus on. Keeps changes contained.
- constraints -- what NOT to do. Surprisingly important for good results.
API tokens stored with chmod 600:
GITHUB_TOKEN=ghp_...
YOUTRACK_TOKEN=perm:...
TRELLO_KEY=...
TRELLO_TOKEN=...
| Source | Filter | On completion |
|---|---|---|
| YAML | tasks: in .nightshift.yaml, status: pending |
Sets status to done |
| GitHub Issues | label: nightshift, state: open |
Closes issue + comment with PR link |
| YouTrack | tag: nightshift |
Removes tag + posts comment |
| Trello | list: "NightShift Queue" | Moves card to "Done" |
| Built-in | Added via TUI [t] with frequency (once/weekly/monthly) |
Once: removed. Weekly/monthly: auto-requeued |
NightShift is paranoid by design:
- Draft PRs only -- never pushes to main, never merges anything. You always have the final say.
- Quality gates -- blast radius limits (max files/lines changed), linter checks, test regression detection. If anything looks off, the task fails and no PR is created.
- Claude Code runs with
--dangerously-skip-permissions-- required for unattended operation. Only run on repositories you trust. - Tokens stored in
~/.nightshift/.envwithchmod 600. Never logged, never committed.
| Command | Description |
|---|---|
nightshift |
Launch TUI dashboard |
nightshift init |
Interactive setup wizard |
nightshift add |
Add a project to existing config |
nightshift sync |
Import tasks from configured sources |
nightshift run [--dry-run] [-p PROJECT] |
Execute a run |
nightshift status |
Show latest run results |
nightshift log [N] |
Run history / task details |
nightshift tasks list |
Show task queue |
nightshift tasks add |
Add a task manually |
nightshift doctor |
Environment health check |
nightshift install |
Set up scheduled runs (launchd/systemd) |
nightshift uninstall |
Remove scheduled runs |
NightShift supports third-party task sources via Python entry points.
# nightshift_jira/source.py
from nightshift.models.config import SourceConfig
from nightshift.models.task import Task
class JiraSource:
async def fetch_tasks(self, project_path: str, config: SourceConfig) -> list[Task]:
# ... fetch from Jira API
return tasks
async def mark_done(self, task: Task, pr_url: str) -> None:
# ... transition issue, post comment
pass# In your plugin's pyproject.toml
[project.entry-points."nightshift.sources"]
jira = "nightshift_jira.source:JiraSource"# .nightshift.yaml
sources:
- type: jira
options:
url: https://mycompany.atlassian.net
project_key: PROJFor NightShift to run on schedule, the machine must stay awake overnight.
- macOS: System Settings > Energy > Options > "Prevent automatic sleeping when the display is off". Or:
sudo pmset -c disablesleep 1 - Linux:
systemctl mask sleep.target suspend.target
nightshift doctor will warn if sleep prevention is not configured.
git clone https://github.com/androsovm/NightShift.git
cd NightShift
uv sync --extra dev
uv run pytestMIT