-
Notifications
You must be signed in to change notification settings - Fork 34
Description
Environment
- nightshift v0.3.2
- Linux (Arch), systemd user timer
- Providers: Claude + Codex (preference: claude → codex)
Bug 1: schedule.max_projects config value ignored by run command
Expected
Setting schedule.max_projects: 5 in config.yaml should be respected by nightshift run.
Actual
The run command reads max-projects exclusively from the CLI flag (hardcoded default 1) and never consults the config file. The config set schedule.max_projects 5 command accepts and stores the value, but run ignores it.
Source trace (v0.3.2)
cmd/nightshift/commands/run.go:
- Line 107:
runCmd.Flags().Int("max-projects", 1, ...)— hardcoded default - Line 120:
maxProjects, _ := cmd.Flags().GetInt("max-projects")— reads CLI flag only - Lines 198-199: truncates project list to
maxProjectsBEFORE processing
No code path reads schedule.max_projects from config.
Impact
With the default of 1, only the first configured project is ever considered. If that project is skipped ("already processed today") or fails, no other projects are tried for the entire day. The systemd service must be manually edited to pass --max-projects N as a workaround.
Reproduction
nightshift config set schedule.max_projects 5 --global
nightshift config get schedule.max_projects # Returns: 5
nightshift run --dry-run # Shows: "Projects (0 of 1)" — config ignored
nightshift run --dry-run --max-projects 5 # Shows: "Projects (4 of 5)" — CLI flag worksSuggested fix
In runRun(), after loading config, check if the flag was explicitly set; if not, fall back to cfg.Schedule.MaxProjects:
if !cmd.Flags().Changed("max-projects") && cfg.Schedule.MaxProjects > 0 {
maxProjects = cfg.Schedule.MaxProjects
}Bug 2: Budget calibration reports "exhausted" at day boundary despite 0% usage
Expected
At midnight (start of new day), with 0% daily usage and tokens remaining in the weekly pool, Claude provider should have budget available.
Actual
At midnight, nightshift logs:
provider claude: budget exhausted (0.0% used)
This is contradictory — 0% used means nothing was consumed, yet the budget is reported as exhausted. This causes nightshift to fall through to the Codex provider (which then fails separately — see Bug 3).
Conditions
calibrate_enabled: truewith "low confidence, 2 samples"- No
stats-cache.jsonexists (JSONL fallback is always used) - Happens consistently at 00:00 local time on day boundary
- Does NOT happen later in the day (budget shows correctly at 10:00+)
Reproduction
Observed on two consecutive nights:
2026-02-16T00:00:02 — "provider claude: budget exhausted (0.0% used)" → task failed
2026-02-17T00:00:48 — "provider claude: budget exhausted (0.0% used)" → task failed
Successful runs on prior days (Feb 13–15) all occurred at non-midnight times (07:58, 22:00, 11:30).
Workaround
nightshift config set budget.calibrate_enabled false --globalThis uses the static weekly_tokens config value (2.5M) instead of the calibrated estimate, which produces stable budget calculations at all times of day.
Bug 3: Codex provider crashes with exit status 2 in automatic fallback path
Expected
When Claude budget is exhausted and Codex is the fallback provider, Codex should execute the task.
Actual
Codex fails instantly (~55ms) with exit status 2 (CLI argument error) when invoked through the automatic fallback path. However, Codex works correctly when invoked directly:
# Fails (automatic fallback after Claude exhausted):
nightshift run # → "agent execution: exit status 2" in 55ms
# Works (explicit provider):
nightshift task run lint-fix --provider codex --project ~/Sync/code/qtdashboardHypothesis
The automatic fallback path may be passing incorrect CLI flags to the Codex binary — possibly Claude-specific flags like --dangerously-skip-permissions that Codex doesn't recognize (Codex only has --dangerously-bypass-approvals-and-sandbox). The instant failure (55ms) and exit code 2 are consistent with a CLI argument parse error.
Environment details
- Claude config:
dangerouslyskippermissions: true,dangerouslybypassapprovalsandsandbox: false - Codex config:
dangerouslyskippermissions: false,dangerouslybypassapprovalsandsandbox: true