DEV Community

Todd Sullivan
Todd Sullivan

Posted on

My AI Cron Job Failed Because .env Wasn't a Shell Script

I hit a small failure in an AI publishing cron job this morning that is exactly the kind of boring edge that makes agents unreliable.

The job needed one secret:

DEVTO_API_KEY=...
Enter fullscreen mode Exit fullscreen mode

The lazy version was to source the whole env file and let the shell export everything.

set -a
. ~/.hermes/.env
set +a
Enter fullscreen mode Exit fullscreen mode

That is fine until the env file stops being a shell script.

This one had unrelated values in it. One contained an app path with spaces. Another looked enough like shell syntax to be treated as a command. The job still found the API key later, but the logs had the real problem:

Chrome.app/Contents/MacOS/Google: No such file or directory
fyqi: command not found
Enter fullscreen mode Exit fullscreen mode

Nothing about the Dev.to API was broken. The agent had just widened the trust boundary for no reason.

The fix was not to make the env parser clever. It was to stop parsing the file as code.

import re
from pathlib import Path

text = Path.home().joinpath(".hermes/.env").read_text(errors="ignore")
match = re.search(r"^DEVTO_API_KEY=(.*)$", text, re.M)
api_key = match.group(1).strip().strip('"\'')
Enter fullscreen mode Exit fullscreen mode

That is deliberately boring. It reads exactly the value the job needs and ignores everything else.

The useful rule for agent jobs is:

if a task needs one credential, load one credential.

Do not source a shared env file just because it is convenient. AI agents already have enough ways to do surprising things. Giving a scheduled job a file full of executable shell lines is a cheap way to add another one.

This is especially true for personal agent infrastructure, where the same .env tends to collect API keys, browser paths, tunnel tokens, feature flags, old experiments, and comments that were never meant to be interpreted by sh.

The failure mode is annoying rather than dramatic, which is why it is worth fixing. The job does not need a config framework. It just needs to stop executing config.

Small boundary. Small parser. Fewer weird mornings.

Top comments (0)