Skip to content

feat(deploy): hermes-agent autodeploy unit + timer (DAN-2730) - #189

Merged
dizhaky merged 1 commit into
mainfrom
claude/agents-loops-graphs-kzjvme
Aug 13, 2026
Merged

feat(deploy): hermes-agent autodeploy unit + timer (DAN-2730)#189
dizhaky merged 1 commit into
mainfrom
claude/agents-loops-graphs-kzjvme

Conversation

@dizhaky

@dizhaky dizhaky commented Aug 13, 2026

Copy link
Copy Markdown
Owner

What does this PR do?

Adds a systemd autodeploy companion to hermes-gateway.service: a oneshot unit + timer that fast-forwards a hermes-agent checkout to its tracked branch and restarts the gateway only when the checkout actually moved. Idempotent and quiet — when already current it exits 0 without touching the gateway or emitting an alert.

Motivation: merges to main currently don't reach a running gateway until someone manually pulls and restarts (this is exactly what happened deploying #188 to a live host). The restart deliberately lives in a systemd/launchd path, not hermes cron — the cron lifecycle_guard blocks jobs from restarting the gateway they run under.

Related Issue

Fixes DAN-2730.

Type of Change

  • ✨ New feature (non-breaking change that adds functionality)

Changes Made

  • deploy/hermes-agent-autodeploy.sh — the deploy script. Parameterized via env (HERMES_REPO_DIR, HERMES_BRANCH, HERMES_RESTART_UNIT, HERMES_SYSTEMCTL_ARGS, optional HERMES_VENV_SYNC, SLACK_WEBHOOK_URL, HERMES_ALERT_LABEL) so one script serves both a system-level (User=hermes) and a rootless systemctl --user deploy.
  • deploy/hermes-agent-autodeploy.serviceType=oneshot; StartLimit* correctly in [Unit]; hardening mirrors hermes-gateway.service; install docs for both system- and user-level in the header.
  • deploy/hermes-agent-autodeploy.timerOnBootSec=5min, OnUnitActiveSec=15min, Persistent=true, RandomizedDelaySec=90.

Safety properties: refuses a dirty working tree; fast-forward only (never advances past a diverged/force-pushed history — surfaces for manual resolution); flock against overlapping timer fires; runs the venv sync only when uv.lock/pyproject.toml/requirements.txt changed across the fast-forward; a failed venv sync aborts before the restart so a half-updated gateway never starts. Alerts post one line to Slack when SLACK_WEBHOOK_URL is set and always mirror to the journal; alert failures never abort the deploy result.

How to Test

Verified the four control-flow paths against a scratch git repo with a stubbed systemctl:

  1. Already current → silent no-op, exit 0, no restart.
  2. Behind → fast-forward + restart, exit 0, checkout advanced to origin.
  3. Dirty tree → refuse, exit 1.
  4. Diverged local history → refuse with an explicit message, exit 1.

Also bash -n clean; systemd-analyze verify passes except for the expected "ExecStart not present" on a host where the script isn't installed to /usr/local/bin.

Checklist

Code

  • Conventional Commits (feat(deploy): ...)
  • Searched for existing PRs — none for this
  • PR contains only changes related to this feature
  • Tested on my platform: Linux (scratch-repo harness)

Documentation & Housekeeping

  • Install/verify docs live in the unit headers — or N/A
  • cli-config.yaml.example — N/A (no config keys)
  • AGENTS.md — N/A (deploy artifact, no core/agent behavior change)
  • Cross-platform impact considered — systemd unit; user-level path documented for rootless hosts

🤖 Generated with Claude Code

https://claude.ai/code/session_01MW1vwtetBTLSMJcBYp9r1C


Generated by Claude Code

Companion to hermes-gateway.service: a oneshot systemd unit + timer that
fast-forwards a hermes-agent checkout to its tracked branch and restarts
the gateway ONLY when the checkout moved. Idempotent and quiet — exits 0
without touching the gateway when already current.

The restart deliberately lives in a systemd/launchd path, not hermes cron:
the cron lifecycle_guard blocks jobs from restarting the gateway they run
under. Parameterized via env (repo dir, branch, restart unit, systemctl
scope, optional venv sync + Slack webhook) so one script serves both a
system-level (User=hermes) and a rootless user-level deploy.

Safety: refuses a dirty tree, fast-forward only (never force past a
diverged/force-pushed history), flock against overlapping timer fires,
and runs the venv sync only when uv.lock/pyproject/requirements changed.
Core paths (no-op / ff+restart / dirty / diverged) verified against a
scratch repo with stubbed systemctl.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MW1vwtetBTLSMJcBYp9r1C
@linear-code

linear-code Bot commented Aug 13, 2026

Copy link
Copy Markdown

DAN-2730

@dizhaky
dizhaky marked this pull request as ready for review August 13, 2026 15:31
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@dizhaky
dizhaky merged commit 3060151 into main Aug 13, 2026
46 checks passed
@dizhaky
dizhaky deleted the claude/agents-loops-graphs-kzjvme branch August 13, 2026 15:32
github-actions Bot pushed a commit that referenced this pull request Aug 17, 2026
Resolves Dependabot alert #189 (GHSA-2v37-7h3g-55p8 / CVE-2026-67213).
nanoid before 3.3.18 can loop indefinitely when customAlphabet or
customRandom is called with size=0, causing a denial-of-service.
nanoid is a transitive dependency via postcss in the website/
Docusaurus sub-project; not directly imported in any source file.
Updates both the overrides entry and the lockfile resolution.
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.

2 participants