Skip to content

fix(cron): allow profile-scoped jobs to reference default-profile scripts - #40918

Closed
maxmilian wants to merge 1 commit into
NousResearch:mainfrom
maxmilian:fix/40801-cron-default-profile-scripts
Closed

fix(cron): allow profile-scoped jobs to reference default-profile scripts#40918
maxmilian wants to merge 1 commit into
NousResearch:mainfrom
maxmilian:fix/40801-cron-default-profile-scripts

Conversation

@maxmilian

Copy link
Copy Markdown
Contributor

Summary

Fixes #40801. A profile-scoped cron job runs with a profile-switched home (~/.hermes/profiles/<name>), so _run_job_script validated the script path against only that profile's scripts dir. Shared/canonical scripts kept in the default-profile scripts dir (~/.hermes/scripts/) were hard-rejected for all three reference shapes — relative name, absolute path, and profile-local symlink whose realpath lands in the default dir — even though that dir is a sanctioned Hermes location. This is the inverse of #32091: routing profile: <name> jobs through the default scheduler (with profile-switched execution at fire time) narrowed script resolution to the profile-local dir.

Root cause

cron/scheduler.py::_run_job_script built a single scripts_dir = _get_hermes_home()/scripts and rejected any resolved path not relative_to it. Under profile switch _get_hermes_home() is ~/.hermes/profiles/<name>, so the canonical ~/.hermes/scripts/ is "outside".

Fix

Accept a script resolving into either the profile-local or the default-profile scripts dir (get_default_hermes_root()/scripts — already the SSOT helper that walks a profile home up to its root). For relative script names, resolve profile-local first then fall through to default-profile, mirroring how skill resolution walks the profile catalog before the default catalog (reporter's Option 3, combined with Option 2 for absolute/symlink). The traversal/injection guard is unchanged in intent — both targets are Hermes-owned dirs; absolute paths outside any scripts dir (/etc/passwd, ../) stay blocked. In non-profile mode get_default_hermes_root() equals the home, so allowed_dirs dedups to one and behavior is byte-for-byte unchanged.

Tests

scripts/run_tests.sh tests/cron/407/407 passing (+5 new). New regression tests under a profile-switched env cover: absolute path to default-profile script, relative fall-through to default-profile, profile-local precedence when the same name exists in both, profile-local symlink → default-profile realpath, and the still-blocked /etc/passwd escape. scripts/check-windows-footguns.py clean.

Scope

Only _run_job_script path validation. Does not change interpreter selection, secret redaction, timeout, or the profile-switch routing from #32091. Does not touch the error message's suggested remedy beyond widening what's accepted.

Follow-up

The Blocked: message still names only the profile-local dir; a future polish could list both sanctioned dirs to point users at the right remedy, but that's cosmetic and out of scope here.

@maxmilian
maxmilian force-pushed the fix/40801-cron-default-profile-scripts branch from 0b58ae4 to 1c97582 Compare June 7, 2026 02:22
…ipts

A profile-scoped cron job runs with a profile-switched home
(~/.hermes/profiles/<name>), so _run_job_script validated the script
path against only that profile's scripts dir. Shared/canonical scripts
that live in the default-profile scripts dir (~/.hermes/scripts/) were
hard-rejected for all three reference shapes — relative name, absolute
path, and profile-local symlink whose realpath lands in the default dir
— even though the default-profile scripts dir is a sanctioned Hermes
location. This is the inverse of NousResearch#32091: routing profile jobs through the
default scheduler narrowed script resolution to the profile-local dir.

Accept a script that resolves into either the profile-local OR the
default-profile scripts dir, and for relative names resolve profile-local
first then fall through to default-profile (mirroring skill resolution:
profile catalog before default catalog). The traversal/injection guard is
unchanged in intent — both targets are Hermes-owned dirs, and absolute
paths outside any scripts dir (/etc/passwd, ../) stay blocked. In
non-profile mode get_default_hermes_root() equals the home, so allowed_dirs
dedups to one entry and behavior is unchanged.

Adds regression tests for the three previously-failing shapes, profile-local
precedence, the still-blocked escape, and root resolution via both the
Docker grandparent layout and the standard-install relative_to branch.

Fixes NousResearch#40801

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@maxmilian
maxmilian force-pushed the fix/40801-cron-default-profile-scripts branch from 1c97582 to f2461e1 Compare June 7, 2026 02:30
@maxmilian
maxmilian marked this pull request as ready for review June 7, 2026 02:44
@alt-glitch alt-glitch added type/bug Something isn't working comp/cron Cron scheduler and job management P2 Medium — degraded but workaround exists labels Jun 7, 2026
@maxmilian

Copy link
Copy Markdown
Contributor Author

Closing — the per-job cron profile support that #40801 and this PR build on was fully reverted on main in #43956 (revert(cron): remove per-job profile support (PR #28124)). Cron jobs no longer switch profiles at fire time, so _run_job_script always validates against the canonical ~/.hermes/scripts/ and the default-profile script can no longer be rejected — the scenario this PR fixes can't occur anymore. If per-job cron profiles are ever reintroduced, this resolution gap is worth revisiting. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cron Cron scheduler and job management P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cron script-path guard rejects profile-scoped jobs referencing default-profile scripts directory (inverse of #32091)

2 participants