---
title: "Why a scheduled codex exec job found no credentials as SYSTEM"
canonical: https://dxdev.com/blog/executor-privilege-context-breaks-auth/
datePublished: 2026-09-23
---
Twenty-six rewritten posts were staged, the stuck promote branch was merged, and nothing published. The run had gone a full pass with no error in the output that anyone read.

The blog lane runs headless. A scheduled task launches the executors, and one of them is `codex exec`, which does the scoring and rewrite passes. On this run its auth was broken for most of the pass. We later traced that to the [executors running as SYSTEM](/blog/nssm-localsystem-homedir-claude-resume-systemprofile/).

## The wrong turn: we believed the queue

The session before this one ran 15 hours. It fixed four bugs that were blocking the pipeline, backfilled the missing lesson blocks across the whole review queue, and wrote nineteen drafts for the gap week on both the technical and non-technical tracks. Its conclusion was that nothing was publishable for content reasons rather than plumbing. It also noted that part of the scoring record looked untrustworthy.

We took the first half of that at face value and ignored the second. The morning's blog run started from it. We merged the promote branch and staged the 26 rewrites on the assumption that the plumbing was sound and the content was the problem, so the rewrites were the fix.

That cost a full pass of rewriting posts against a verdict from a scorer we now had reason to doubt. The run took one minute, and every rewrite in it was aimed at a diagnosis we hadn't checked.

We can't prove the scoring gap and the auth failure are the same event. Both showed up in the same window, and the pipeline reported neither as a failure.

## What SYSTEM does to a CLI login

An interactive `codex` login writes its credentials under the profile of the user who ran it. A scheduled executor running as SYSTEM gets a different profile directory, so it looks for that file and finds nothing. On Windows the exact paths differ by service context, but the mechanism is the same: the login exists for one identity and the job runs as another.

The interactive shell works, because it is the identity that logged in. The scheduled job fails, because it isn't. That is why nothing looked wrong when we ran things by hand.

The failure went unnoticed for a second reason. The executor's output lands as a one-line summary in the session log, and a job that cannot authenticate produces a summary that looks like any other short one. Elsewhere in the same day's log, headless runs that hit a timeout at 900 seconds record `(timeout after 900s)` with no token count and no cost. Those are at least visibly wrong. An auth failure that returns quickly and empty has no such marker.

## The fix, and the check we should have had

We fixed it by making the executors run with credentials that belong to the context they run in. The pipeline logic stayed untouched. After that the executors could authenticate.

The check we would add is a preflight that runs before any lane does real work. It makes one cheap authenticated call as the same identity the lane will use, and it stops the run loudly if the call fails. We'd put it ahead of the scoring step, because a lane that stages 26 posts and publishes zero should read as a failed run, and right now it reads as a quiet one.

The other change is smaller. We no longer treat a "nothing is publishable" verdict as a content finding until we have confirmed the scorer that produced it could authenticate when it ran. A scoring record from a job that may have been running blind is not evidence about the posts.

## Where the run stands

The 26 staged rewrites are still staged. Whether they were the right rewrites is an open question, because the verdict they were built on came from a pipeline that could not have been reliably checking anything. Until the preflight exists, a green run here only tells us the job finished. It doesn't tell us the job did its work.

## The 15 hours before it

The session that preceded all this is written up separately. We fixed four bugs that were blocking the blog pipeline, backfilled the missing lesson blocks across the whole review queue, and wrote nineteen drafts for the gap week, split across our technical and non-technical tracks.

A login made interactively may not exist for the identity a scheduled job runs as. Confirm the job can authenticate as that identity before you trust what it reports, and until a preflight stops the run loudly on an auth failure, treat a "nothing is publishable" verdict as unproven.
