Skip to content

Load env.d in cron jobs so they get GH_TOKEN - #461

Merged
cgraf78 merged 3 commits into
mainfrom
fix/cron-update-gh-token
Nov 2, 2026
Merged

cgraf78 merged 3 commits into
mainfrom
fix/cron-update-gh-token

Conversation

@cgraf78

@cgraf78 cgraf78 commented Nov 2, 2026 •

Copy link
Copy Markdown
Owner

Summary

Cron starts jobs from a bare /bin/sh environment that never loads env.d, so the scheduled dot update --cron --force ran with no GH_TOKEN. Shdeps then made anonymous GitHub API calls, which share one 60-per-hour quota across every host behind the same public IP. A rate-limited run reports dependencies as current instead of updating them, so hosts silently stopped picking up new releases until someone ran dot update by hand.

The cron merge hook now writes SHELL (an absolute bash) and BASH_ENV (env-noninteractive.sh) into the managed crontab block, so every cron job loads env.d fill-only, like agent and script shells. GH_TOKEN comes from env.d/60-github.sh for any job, and the update line stays plain dot update --cron --force.

  • Prefers the system bash (/bin/bash, /usr/bin/bash, or Termux's $PREFIX/bin/bash) over a PATH lookup, so removing a Homebrew, mise, or Shdeps bash cannot leave every job, including the repairing dot update, with a missing shell.
  • Emits nothing when bash or the loader is missing, leaving cron on its default shell.
  • The variables precede PATH inside the block; hand-written crontab lines before the block are unaffected.
  • Behavior change: every managed cron job now runs under bash with env.d loaded, and its bash children inherit BASH_ENV. Each account reads its own token file, so root's crontab needs /root/.config/gh/github-pat.
  • Documents cron in the shell README.

Testing

  • core-cron: the block omits the variables without the loader, sets an absolute bash and the loader with it, orders them before PATH, and a job run with the rendered SHELL/BASH_ENV in a clean environment gets GH_TOKEN with no output; a job-set token wins.
  • Simulated cron jobs (crontab PATH, bash, BASH_ENV) as root and the user on three hosts, two with dash as /bin/sh: token present, no output, same tools found, 4-10 ms load.
  • dot test: 38 suites passed, 1 skipped.
  • DOT_CORE_SHARD=all dot test core: the same two pre-existing failures as origin/main; the new checks pass.
  • checkrun lint: pass.

Review

Two fresh-eyes passes. The first (on the earlier token-file approach) covered rendering, empty-token handling, root, and security. The second covered cron implementations (cronie, Vixie/Debian, macOS, Termux, BusyBox), side effects on existing jobs, and tests; fixed test isolation under all-shard runs and the bash lookup order.

🤖 Generated with Claude Code

cgraf78 and others added 2 commits November 2, 2026 09:46
Summary

Cron starts jobs from a bare `/bin/sh` environment that never loads
`env.d`, so the scheduled `dot update --cron --force` ran with no
`GH_TOKEN`. Shdeps then made anonymous GitHub API calls, which share
one 60-per-hour quota across every host behind the same public IP. A
rate-limited run reports dependencies as current instead of updating
them, so most hosts silently stopped picking up new releases until
someone ran `dot update` by hand.

- the update cron line now reads the first line of
  `~/.config/gh/github-pat`, the file `env.d/60-github.sh` already
  exports, and passes it as `GH_TOKEN`
- a `GH_TOKEN` the caller already set wins, and a missing or empty file
  leaves the token empty, matching the shell's fill-only behavior
- it is a plain POSIX file read, so it works under dash, bash, and
  Termux's sh, and it never calls `gh auth token`, which once leaked
  keyring daemons from cron
- documents the cron path in the shell README, including that root's
  crontab reads root's own token file

Testing

- new `core-cron` cases render the entry through the real merge hook
  with a home path containing a space, then run it under the host's sh
  (and dash when present) in a clean environment with a stub `dot`:
  missing file, token file, caller override, multi-line file, and
  empty file
- ran the same command under dash on a Debian host: no file gives an
  empty token, a token file gives the token
- `dot test`: 38 suites passed, 1 skipped
- `checkrun lint`: pass
- fresh-eyes review on side effects, security, and test quality; now
  tests the rendered line, and documents root's token file

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary

Replaces the token file read in the update cron line with a general
fix: the cron merge hook now writes `SHELL` (an absolute bash) and
`BASH_ENV` (`env-noninteractive.sh`) into the managed crontab block,
so every cron job loads `env.d` fill-only, like agent and script
shells. `GH_TOKEN` then comes from `env.d/60-github.sh` for any job,
the token file keeps a single reader, and the update line is back to
plain `dot update --cron --force`.

- prefers the system bash (`/bin/bash`, `/usr/bin/bash`, or Termux's
  `$PREFIX/bin/bash`) over a PATH lookup, so removing a Homebrew,
  mise, or Shdeps bash cannot leave every job, including the repairing
  `dot update`, with a missing shell
- emits nothing when bash or the loader is missing, leaving cron on its
  default shell
- variables precede `PATH` inside the block; hand-written crontab lines
  before the block are unaffected
- documents cron in the shell README's Environment Ownership section

Testing

- `core-cron`: the block omits the variables without the loader, sets
  an absolute bash and the loader with it, orders them before `PATH`,
  and a job run with the rendered `SHELL`/`BASH_ENV` in a clean
  environment gets `GH_TOKEN` with no output; a job-set token wins
- simulated cron jobs (crontab `PATH`, bash, `BASH_ENV`) as root and
  the user on three hosts, two with dash as `/bin/sh`: token present,
  no output, same tools found, 4-10 ms load
- `dot test`: 38 suites passed, 1 skipped
- `DOT_CORE_SHARD=all dot test core`: the same two pre-existing
  failures as `origin/main`, new checks pass
- `checkrun lint`: pass
- fresh-eyes review on cron implementations, side effects on existing
  jobs, and tests; fixed test isolation and the bash lookup order

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cgraf78 cgraf78 changed the title Pass the GitHub token to the cron dot update Load env.d in cron jobs so they get GH_TOKEN Nov 2, 2026
Summary

Most accounts have no `~/.config/gh/github-pat` (root on a fresh host,
  for example), so the cron env loader must leave such jobs untouched.
  This pins that case with a dedicated check.

- run a job under the rendered `SHELL`/`BASH_ENV` with no token file
  and `set -eu`; it exits cleanly, prints nothing, and leaves
  `GH_TOKEN` unset

Testing

- `dot test -v core-cron`: all checks pass, including the new one
- `shellcheck -x .local/lib/dotfiles/tests/core/cron.sh`

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cgraf78
cgraf78 merged commit 9b6869c into main Nov 2, 2026
22 checks passed
@cgraf78
cgraf78 deleted the fix/cron-update-gh-token branch November 2, 2026 16:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant