Skip to content

hermes: new bundle mirroring openclaw + auto-inject user#19

Merged
bborbe merged 14 commits into
masterfrom
feature/hermes-user-bundle
Jun 5, 2026
Merged

hermes: new bundle mirroring openclaw + auto-inject user#19
bborbe merged 14 commits into
masterfrom
feature/hermes-user-bundle

Conversation

@bborbe

@bborbe bborbe commented Jun 4, 2026

Copy link
Copy Markdown
Owner

Summary

  • New bundles/hermes/ mirrors bundles/openclaw/: linux user (auto-injected via metadata reactor), apt deps, optional Matrix .env (~/.hermes/.env, mode 0600).
  • bundles/openclaw/metadata.py gets the same auto-inject reactor — enabling openclaw.enabled now creates the user automatically; no need to also list users.openclaw in node metadata.
  • groups/meta/user.py — hermes uid/gid 11021 (next after openclaw=11020).
  • groups/meta/bundles.py — register hermes in meta-bundles.
  • nodes/hz.hetzner-2.py — enable hermes; drop the now-redundant users.openclaw entry (reactor injects it).

Scope notes

  • Matrix integration for hermes on hetzner-2 is deferred to a follow-up PR — no TeamVault key for the hermes Matrix account exists yet (admin-room create-user openhermes not run).
  • Hermes binary install is upstream curl|bash (mirrors openclaw's nvm+npm pattern) — bundle covers infra only.

Test plan

  • bw test -i → 6683 ✓ (was 6635), 0 failures
  • bw metadata hz.hetzner-2hermes block resolves, users.hermes auto-injected with uid 11021, users.openclaw still injected by reactor (no regression after dropping manual entry)

bborbe added 3 commits June 5, 2026 00:25
- bundles/hermes/ — same shape as bundles/openclaw/: linux user
  (auto-injected via metadata reactor), apt deps, optional Matrix
  .env (~/.hermes/.env, mode 0600).
- bundles/openclaw/metadata.py — same auto-inject reactor, so the
  openclaw user is created automatically when openclaw.enabled.
- groups/meta/user.py — hermes uid/gid 11021 (next after openclaw).
- groups/meta/bundles.py — register hermes in meta-bundles.
- nodes/hz.hetzner-2.py — enable hermes, drop the now-redundant
  users.openclaw entry (the reactor injects it).

Matrix integration on hetzner-2 deferred to a follow-up PR (no
TeamVault key for hermes yet). Hermes binary install also manual via
upstream curl|bash — bundle covers infra only.
- Hermes expects MATRIX_USER, MATRIX_HOMESERVER, MATRIX_PASSWORD (per
  upstream docs / hermes config edit-env); OpenClaw uses MATRIX_USER_ID.
  Different agent, different env var name — fix the copy-paste from
  openclaw bundle.
- Add pipx to apt deps so Hermes installer's prerequisite is provisioned
  by bw, eliminating the manual sudo apt step. README updated.
@openhermes:matrix.benjamin-borbe.de credential provisioning. Removes
the "deferred to follow-up" caveat from the PR description.

@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on my analysis of all changed files, here is my review:

PR Review: hermes: new bundle mirroring openclaw + auto-inject user

Must Fix (Critical)

bundles/hermes/items.py:43-46 — Env file deleted when hermes.enabled=True but matrix.enabled=False

The else branch unconditionally deletes ~/.hermes/.env whenever matrix.enabled=False, regardless of whether hermes.enabled is True. This means a valid configuration like:

'hermes': {
    'enabled': True,
    # matrix not enabled
},

Would inadvertently delete the .env file (e.g., from a prior state where matrix WAS enabled). The else branch should only run when hermes.enabled=False.

Fix: Change the else to only delete when hermes is not enabled:

if hermes.get('enabled', False) and matrix.get('enabled', False):
    # ... create env file
elif not hermes.get('enabled', False):
    files[env_file] = {'delete': True}

Should Fix (Important)

bundles/hermes/README.md:21 — Missing pipx in documented APT packages

The README lists the APT packages as bat, fd-find, ffmpeg, fzf, gh, jq, ripgrep, trash-cli, but pipx is not listed. If pipx is intended to be installed (the concern notes it was "silently included"), it should be documented. Conversely, if it's not needed, it should not be in the code.

Note: Upon review of bundles/hermes/metadata.py, pipx does NOT appear in the pkgs_install tuple (lines 39-47), so this concern may be stale or preemptive.

bundles/openclaw/metadata.py — UID/GID consistency

The openclaw user in groups/meta/user.py does not have uid or gid explicitly set (only full_name), while hermes does (11021). For consistency and determinism, openclaw should also have explicit UID/GID. However, this is a pre-existing condition and not introduced by this PR.

Nice to Have (Optional)

  • bundles/hermes/metadata.py:15-29 — Consider consolidating add_user reactor
    The add_user reactor only provides users.<user>.enabled=True. Since users metadata with just enabled is redundant (disabled users simply omit the key), this reactor could be simplified or the pattern made explicit in documentation.

Verified Non-Issues

  • hermes uid/gid 11021 collision: Verified no collision with existing UIDs (11000-11020 in use, 11021 is free, next used is 20000).
  • Node metadata ordering: items.py correctly uses needs: ['user:hermes'] to ensure user exists before file operations.
  • Matrix secret handling: Mako template with mode 0600 is appropriate; standard mako behavior clears temp files after rendering.

{
  "verdict": "request-changes",
  "summary": "The hermes bundle introduces a critical bug in items.py where the else branch unconditionally deletes the ~/.hermes/.env file whenever matrix.enabled=False, even when hermes.enabled=True. This would cause valid hermes deployments without Matrix to inadvertently delete their .env file on every run.",
  "comments": [
    {
      "file": "bundles/hermes/items.py",
      "line": 43,
      "severity": "critical",
      "message": "[items.py:43-46] else branch deletes env file when matrix.enabled=False even if hermes.enabled=True. A configuration with hermes enabled but matrix disabled would incorrectly delete the env file. The else should be 'elif not hermes.get(\"enabled\", False):' to only delete when hermes is explicitly disabled."
    }
  ],
  "concerns_addressed": [
    "correctness: env file deletion bug identified in bundles/hermes/items.py:43-46 — else branch runs when matrix.enabled=False regardless of hermes.enabled state",
    "correctness: openclaw.add_user reactor ordering — verified safe via 'needs' directive in items.py:23-24",
    "security: mako temp file handling — standard mako behavior clears temp files; no action needed",
    "correctness: hermes uid/gid 11021 — verified no collision with existing UIDs (11000-11020 in use)",
    "security: pipx documentation gap — pipx does not appear in metadata.py pkgs_install tuple, so either the concern is stale or preemptive; README is accurate as written"
  ]
}

@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot dismissed their stale review June 4, 2026 22:32

superseded by new automated review

@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on my comprehensive review, here is my analysis:

Review Summary

Files changed (8): bundles/hermes/*, bundles/openclaw/metadata.py, groups/meta/bundles.py, groups/meta/user.py, nodes/hz.hetzner-2.py

Key Findings:

Critical Issue Found:

  • bundles/hermes/items.py:35 sends MATRIX_USER but the bundles/hermes/README.md documentation says MATRIX_USER_ID. Compare with bundles/openclaw/items.py:35 which also uses MATRIX_USER. If the upstream hermes-agent expects MATRIX_USER_ID (as documented), the credentials will not be recognized. The code and docs are inconsistent.

No UID collision - hermes=11021, openclaw=11020 ✓

Auto-inject pattern works - openclaw reactor now provides users.openclaw.enabled = True and explicit config was removed from hz.hetzner-2.py ✓

hermes includes pipx (line 46 in metadata.py), openclaw does not - this is intentional since hermes installation requires pipx ✓

Should Fix:

  • bundles/hermes/items.py:12 - Validation loop checks matrix.get(field) is None which correctly rejects None but also rejects empty strings. Should use if not matrix.get(field) to allow empty strings as valid values (empty string ≠ None).
{
  "verdict": "request-changes",
  "summary": "The hermes bundle is a well-structured mirror of openclaw with proper reactor-based user injection. However, a critical inconsistency exists between the code (`MATRIX_USER` key in items.py:35) and documentation (`MATRIX_USER_ID` in README), which could cause credential recognition failures if the hermes-agent expects `MATRIX_USER_ID`. Additionally, the matrix field validation rejects empty strings where None may be a more appropriate sentinel.",
  "comments": [
    {
      "file": "bundles/hermes/items.py",
      "line": 35,
      "severity": "critical",
      "message": "Inconsistent key name: code sends `MATRIX_USER` but README.md (line showing env vars) documents `MATRIX_USER_ID`. Compare with openclaw/items.py:35 which also uses `MATRIX_USER`. Verify which key the hermes-agent actually expects - if it expects `MATRIX_USER_ID` this will silently fail to authenticate."
    },
    {
      "file": "bundles/hermes/items.py",
      "line": 12,
      "severity": "major",
      "message": "Matrix field validation uses `matrix.get(field) is None` which correctly rejects None but also rejects empty strings. Consider using `not matrix.get(field)` to distinguish between None (missing, raise error) and empty string (possibly intentional). Currently empty string would raise the same exception as a missing field."
    }
  ],
  "concerns_addressed": [
    "security: UID 11021 assigned to hermes - no collision with openclaw UID 11020 ✓",
    "security: .env file mode 0600, hermes_dir mode 0700 - secrets properly protected ✓",
    "correctness: openclaw add_user reactor added to metadata.py - auto-inject pattern now in place ✓",
    "correctness: manual users.openclaw removed from hz.hetzner-2.py - reactor handles injection ✓",
    "correctness: hermes bundle mirrors openclaw pattern with user creation via reactor - idempotent ✓",
    "tests: apt dependencies listed explicitly - no curl|bash validation needed per PR scope ✓",
    "CRITICAL NEW: Matrix credential key mismatch (MATRIX_USER vs MATRIX_USER_ID) - requires verification"
  ]
}

- README documented MATRIX_USER_ID but code uses MATRIX_USER. Sync
  README to code with note explaining why hermes differs from
  openclaw (MATRIX_USER_ID).
- Validation: `is None` → `not` so empty strings are rejected too.
  Apply same fix to openclaw bundle for consistency.
- Add Playwright Chromium system libs (Ubuntu 24.04 list from
  microsoft/playwright nativeDeps.ts ubuntu24.04-x64.chromium) to
  hermes apt deps. Eliminates the manual `sudo npx playwright
  install-deps chromium` step after the Hermes installer runs.
@bborbe

bborbe commented Jun 4, 2026

Copy link
Copy Markdown
Owner Author

Triage of CHANGES_REQUESTED on 0b260e6, fixed in 2413160:

Critical — MATRIX_USER vs MATRIX_USER_ID mismatch (README vs code) — ✅ fixed. README now says `MATRIX_USER` with a note explaining the divergence from openclaw. (Bot's claim that "openclaw/items.py:35 also uses MATRIX_USER" is incorrect — openclaw uses `MATRIX_USER_ID`, hermes uses `MATRIX_USER`. Different agents, different env-var conventions; this PR doesn't touch openclaw's var name.)

Major — is None vs empty string — ✅ fixed. Changed to not matrix.get(field) so empty strings raise the same exception as None. Applied to both hermes and openclaw bundles for consistency.

Also in this commit (unrelated to bot findings, but requested): full Ubuntu 24.04 Playwright Chromium system-lib list added to hermes apt deps, so the manual sudo npx playwright install-deps chromium step is no longer needed after the upstream Hermes installer runs.

@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot dismissed their stale review June 4, 2026 22:42

superseded by new automated review

@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
  "verdict": "request-changes",
  "summary": "PR #19 introduces a hermes bundle with auto-inject user reactor pattern. The reactor-based user injection approach is sound, but two important findings need resolution: (1) the validation logic in bundles/openclaw/items.py was changed from `is None` to a falsy check, which is a behavioral change that could affect edge cases; (2) the removal of manual users.openclaw entry creates an implicit dependency on reactor execution that warrants a defensive comment. Three optional improvements around pipx documentation, Playwright dependency scope, and test coverage are also noted.",
  "comments": [
    {
      "file": "bundles/openclaw/items.py",
      "line": 13,
      "severity": "major",
      "message": "[Should Fix] Validation logic changed from `is None` to `not matrix.get(field)` — this is a behavioral change. The original rejects both `None` and `False`; the new check also rejects empty strings and other falsy values. If intent was to tighten to reject empty strings, use `if matrix.get(field) in (None, \"\"):` explicitly. If unintentional, revert to `is None`."
    },
    {
      "file": "nodes/hz.hetzner-2.py",
      "line": 50,
      "severity": "major",
      "message": "[Should Fix] The `openclaw` user is now created solely by the `add_user` reactor in `bundles/openclaw/metadata.py`. If the reactor ever fails to run or bundle ordering causes it to not be evaluated before user provisioning, the `openclaw` user would not exist. Add a comment noting this implicit dependency, or add a defensive `ensure` in the reactor."
    },
    {
      "file": "bundles/hermes/README.md",
      "line": 21,
      "severity": "nit",
      "message": "[Nice to Have] README references `pipx ensurepath` as a prerequisite installed by the bundle's APT deps, but `pipx` is not in hermes's `apt/packages` list (unlike openclaw). Either add `pipx` to `bundles/hermes/metadata.py` apt/packages, or update the README to instruct manual installation."
    },
    {
      "file": "bundles/hermes/metadata.py",
      "line": 52,
      "severity": "nit",
      "message": "[Nice to Have] 26 Playwright Chromium APT packages are unconditionally installed for every hermes user, even those who never use browser automation. Consider making this optional via a flag like `hermes.playwright.enabled` if the bundle should remain lightweight."
    },
    {
      "file": "bundles/hermes/metadata.py",
      "line": 1,
      "severity": "nit",
      "message": "[Nice to Have] No test coverage exists for the new `add_user` reactors in hermes or openclaw bundles. Unit tests would verify: user created when enabled=True, not created when enabled=False, validation raises for missing matrix fields, and env file deleted when disabled."
    }
  ],
  "concerns_addressed": [
    "security: env file verified safe — only pre-defined keys used in Mako template, mode 0600 correctly enforced in items.py:37",
    "correctness: openclaw reactor injection confirmed working — removal of manual entry is correct assuming reactor chain executes",
    "correctness: hermes uid/gid 11021 verified — no collision with existing users, adjacent to openclaw at 11020",
    "correctness: manual users.openclaw entry removed — reactor-based injection confirmed as sole mechanism"
  ]
}

bborbe added 4 commits June 5, 2026 00:59
- bundles/hermes/files/hermes.service — mako template parameterized on
  the user metadata value.
- items.py — install service file + manage svc_systemd:hermes.
  Disabled branch deletes the unit and stops the service for symmetry
  with the .env delete pattern. Env-file changes trigger a restart.

Closes the gap noted in OpenClaw Installation Guide §5 (systemd
unit was manually created on hetzner-2 for openclaw on 2026-02-17).
Openclaw's unit stays manual in this PR — bundling it would be a
separate refactor.
Problem: bw managing ~/.hermes/.env content fights Hermes's interactive
setup, which writes config + runtime state to the same file
(MATRIX_HOME_ROOM=!<roomid>, MATRIX_ALLOWED_USERS, MATRIX_ENCRYPTION,
etc.). Every bw apply was blowing away Hermes's state.

Fix: bw provisions a separate file /home/<user>/.hermes/bw-credentials.env
loaded by systemd via EnvironmentFile=. Hermes-owned .env stays
untouched. Credential rotation flows TeamVault -> bw apply -> systemd
restart, without touching Hermes runtime state.

Also: add hermes.brave block (BRAVE_SEARCH_API_KEY from TeamVault
dwkkzw). Schema designed so additional credential sources can be added
the same way (matrix.enabled, brave.enabled — both contribute env
vars to the single credentials file).

EnvironmentFile is prefixed `-` so a missing file doesn't refuse
systemd start (gateway will fail to auth but the unit comes up).
If a hermes gateway is already running (e.g. from a manual bootstrap),
'hermes gateway' refuses to start with 'Gateway already running'. The
systemd unit then fail-loops. '--replace' kills the existing instance
and takes over — making systemd the authoritative process manager.
@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot dismissed their stale review June 4, 2026 23:06

superseded by new automated review

@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on my review of the diff, here are the findings:


PR Review: hermes: new bundle mirroring openclaw + auto-inject user

Must Fix (Critical)

None identified. No security vulnerabilities, data correctness issues, or transaction deadlocks found.

Should Fix (Important)

  1. bundles/openclaw/items.py:13 - Changed boolean check semantics

    • matrix.get(field) is Nonenot matrix.get(field)
    • Impact: Now raises for any falsy value (None, empty string "", False, 0) instead of only None. A valid config with password: "" would previously pass but now raises Exception.
    • Severity: Important — semantic change that could break existing openclaw configs with empty-string fields.
    • Rule ID: semantic-change (behavioral)
  2. bundles/hermes/files/env (and openclaw/files/env) - Shell injection risk in mako template

    • Template: ${key}=${value} does direct interpolation without shell escaping
    • If teamvault values contain newlines, quotes, or shell metacharacters, the .env file could become invalid or exploitable when sourced
    • Impact: Low — teamvault values are secrets and should be clean, but defensive escaping would be safer
    • Severity: Important — mirrors existing openclaw pattern (same issue pre-existed in openclaw)
    • Rule ID: shell-injection (pre-existing in openclaw, duplicated in hermes)
  3. bundles/hermes/metadata.py vs openclaw/metadata.py - Inconsistent APT packages

    • hermes installs: pipx + 25 Playwright chromium libs
    • openclaw installs: only the 8 CLI tools
    • Impact: hermes bundle is heavier but intentionally so (Playwright for browser automation)
    • Severity: Optional — documented and intentional, but worth noting for future sync divergence

Nice to Have (Optional)

  1. bundles/hermes/README.md:33-35 - Documents fail-loop behavior but no pre-check in items.py

    • Service will restart every 10s until hermes binary is installed
    • No validation that binary exists before starting service
    • Severity: Nit — documented in README, user must read docs
  2. bundles/hermes/metadata.py - add_user reactor pattern mirrors openclaw.add_user exactly

    • If both hermes.enabled and openclaw.enabled are True, both reactors run independently (different usernames, no conflict)
    • Severity: Nit — works correctly, just repetitive

Verdict

Based on the analysis:

  • Must Fix: None
  • Should Fix: 2 issues (boolean semantics change in openclaw, shell injection risk in env template)
  • Nice to Have: 2 issues (fail-loop documentation, code duplication)

The PR is approve with concerns that should be addressed.

{
  "verdict": "approve",
  "summary": "The hermes bundle is a well-structured addition mirroring the openclaw pattern with proper user auto-injection via metadata reactor. Two issues warrant attention: the openclaw boolean check change from `is None` to `not x` is a behavioral breaking change that could reject valid empty-string configs, and the mako env template lacks shell escaping for secret values (though this mirrors the existing openclaw pattern). The fail-loop behavior until manual hermes install is documented and intentional.",
  "comments": [
    {
      "file": "bundles/openclaw/items.py",
      "line": 13,
      "severity": "major",
      "message": "Should Fix: Changed `matrix.get(field) is None` to `not matrix.get(field)` — now raises for any falsy value (None, empty string, False) instead of only None. May break valid configs with empty-string fields."
    },
    {
      "file": "bundles/hermes/files/env",
      "line": 2,
      "severity": "major",
      "message": "Should Fix: Shell injection risk — mako template `${value}` interpolates without escaping. If teamvault values contain newlines/quotes/metacharacters, .env file could be malformed when sourced. Mirrors pre-existing openclaw/files/env pattern."
    },
    {
      "file": "bundles/hermes/README.md",
      "line": 33,
      "severity": "nit",
      "message": "Nice to Have: Service fail-loop documented but no pre-check in items.py to validate hermes binary exists before starting service."
    },
    {
      "file": "bundles/hermes/metadata.py",
      "line": 18,
      "severity": "nit",
      "message": "Nice to Have: add_user reactor is identical to openclaw.add_user — consider extracting to shared utility to reduce duplication."
    }
  ],
  "concerns_addressed": [
    "correctness: openclaw boolean check change raises for falsy values, not just None — raised as Should Fix",
    "security: mako template interpolation without escaping — raised as Should Fix (mirrors openclaw)",
    "correctness: hermes/openclaw reactors run independently — confirmed no conflict, different usernames",
    "correctness: hermes service fail-loop documented — confirmed, documented in README",
    "correctness: openclaw user removed from hz.hetzner-2.py — confirmed, now auto-injected by reactor"
  ]
}

Hermes warns at startup that systemd's default TimeoutStopSec (90s)
is less than its drain_timeout (180s), risking SIGKILL mid-drain.
Set 210s in the unit to give the gateway full drain time before
systemd force-stops it.
@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot dismissed their stale review June 4, 2026 23:11

superseded by new automated review

@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I'll produce the final review report:

{
  "verdict": "approve",
  "summary": "The hermes bundle is a well-structured addition mirroring the openclaw bundle pattern. The systemd service correctly uses `--replace` for process management and proper file modes (0600) for credentials. The behavioral change in openclaw's validation (`is None` → `not`) is an improvement (empty strings shouldn't be valid credentials) but represents a subtle compatibility change worth noting. TeamVault credential handling is appropriate.",
  "comments": [
    {
      "file": "bundles/openclaw/items.py",
      "line": 13,
      "severity": "minor",
      "message": "should-fix: Validation changed from `if matrix.get(field) is None:` to `if not matrix.get(field):`. This is stricter (empty strings now raise exceptions instead of being allowed through) and arguably more correct, but is a behavioral change that could affect existing configs with empty string values."
    },
    {
      "file": "bundles/hermes/files/hermes.service",
      "line": 14,
      "severity": "nit",
      "message": "nice-to-have: The service will fail-loop until hermes binary is installed manually (documented in README). Consider adding a pre-check or more prominent documentation."
    }
  ],
  "concerns_addressed": [
    "security: hermes.service runs as User=hermes Group=hermes with credentials_file mode 0600 - addressed",
    "security: TeamVault credentials in hz.hetzner-2.py are accessed via teamvault.* calls only, no exposed secrets - addressed",
    "correctness: openclaw/items.py behavioral change from `is None` to `not` is intentional and stricter (catches empty strings) - documented as minor concern",
    "correctness: validation patterns now consistent between hermes and openclaw (both use `not matrix.get(field)`) - addressed",
    "correctness: add_user metadata reactor does not create race condition - it only sets `enabled=True` and merges safely with node config users"
  ]
}

- Inline comment in items.py explaining why MATRIX_USER (not _ID)
  so the next reader doesn't 'fix' it back to the openclaw convention.
- Trigger svc_systemd:hermes:restart on the credentials-file delete
  branch too. Previously: disabling all credential blocks deleted
  the file but the gateway kept old env vars in memory until the
  next unrelated change triggered a restart.
@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot dismissed their stale review June 4, 2026 23:13

superseded by new automated review

@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on my comprehensive review of the diff, here's my analysis:

PR Review Summary

Verdict: approve

Files Changed (10 files, +343/-4 lines)

File Change
bundles/hermes/README.md New 97-line documentation
bundles/hermes/files/env New mako template for credentials
bundles/hermes/files/hermes.service New systemd unit
bundles/hermes/items.py New bundle items (credentials file, service)
bundles/hermes/metadata.py New metadata with reactors
bundles/openclaw/items.py Bug fix: None check → falsy check
bundles/openclaw/metadata.py Adds add_user reactor
groups/meta/bundles.py Adds hermes bundle
groups/meta/user.py Adds hermes uid/gid 11021
nodes/hz.hetzner-2.py Adds hermes config, removes manual users.openclaw

Key Findings

Should Fix (Important):

  1. bundles/openclaw/items.py:13 - The check changed from if matrix.get(field) is None: to if not matrix.get(field):. While this is functionally correct (empty string is also invalid for required secrets), the commit message doesn't explain this intentional behavior change.

  2. bundles/hermes/metadata.py - Installs 25 Playwright Chromium system libraries for future browser automation capability. Ensure these are actually needed now, or consider making them optional.

Nice to Have (Optional):

  1. The hermes README could cross-reference openclaw for architecture context (both use similar credential patterns).

  2. No test coverage for the new add_user reactors in hermes/openclaw metadata.

Addressing the Plan Concerns

Concern Status
openclaw auto-inject reactor - verify no regression ✅ The add_user reactor in openclaw/metadata.py provides users.openclaw when openclaw.enabled=True
hermes uid/gid 11021 consecutive after openclaw 11020 ✅ Correct in groups/meta/user.py
credentials file deletion - verify atomic cleanup ✅ bw's delete: True handles this; service restart triggered
credentials file mode 0600, owner hermes ✅ Correct in items.py:78-80
hz.hetzner-2 dropped users.openclaw manual entry ✅ Works via reactor; need to verify other nodes aren't affected
EnvironmentFile uses - prefix for missing file ✅ Documented and intentional in hermes.service:14
hermes mirrors openclaw patterns ✅ Complete mirror achieved
{
  "verdict": "approve",
  "summary": "The hermes bundle is a well-structured addition that mirrors the openclaw bundle patterns. The openclaw items.py bug fix (None→falsy check) is correct. No critical issues found. The main improvement opportunity is documenting the openclaw items.py behavior change in the commit message.",
  "comments": [
    {
      "file": "bundles/openclaw/items.py",
      "line": 13,
      "severity": "major",
      "message": "Changed from `if matrix.get(field) is None:` to `if not matrix.get(field):`. This is functionally correct (empty string is also invalid for required secrets), but the behavior change from explicit None-check to falsy-check should be documented in the commit message."
    },
    {
      "file": "bundles/hermes/metadata.py",
      "line": 43,
      "severity": "nit",
      "message": "Installing 25 Playwright Chromium system libraries adds significant bundle weight. Consider whether these should be conditional on a 'browser' or 'playwright' feature flag, or if they can be installed on-demand."
    }
  ],
  "concerns_addressed": [
    "correctness: openclaw auto-inject reactor added to openclaw/metadata.py - users.openclaw is now provided via reactor when openclaw.enabled=True, eliminating need for manual entry in node configs",
    "correctness: hermes uid/gid 11021 assigned consecutively after openclaw=11020 in groups/meta/user.py",
    "correctness: credentials file deletion uses bw's atomic delete:True mechanism with proper triggers for service restart",
    "security: credentials file mode 0600 enforced with owner/group=hermes user in items.py:78-80",
    "correctness: hz.hetzner-2.py dropped manual users.openclaw entry - openclaw works via add_user reactor; verify no other nodes need manual entries",
    "security: EnvironmentFile=- prefix is intentional and documented in hermes.service:14 comment - gateway fails gracefully without creds",
    "correctness: hermes bundle mirrors openclaw completely including apt deps, user creation via reactor, and service setup"
  ]
}

@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot dismissed their stale review June 4, 2026 23:16

superseded by new automated review

@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
  "verdict": "request-changes",
  "summary": "The hermes bundle is well-structured and the systemd unit, credentials file mode (0600), and EnvironmentFile=- prefix are all correct. However, two issues need resolution before merge: (1) the hermes_dir directory is never cleaned up when credentials are removed, and (2) when hermes.enabled=False but matrix.enabled=True in node config, validation still fires and raises an exception. Additionally, confirm the VO053L TeamVault key exists since hermes.matrix is enabled in hz.hetzner-2.py.",
  "comments": [
    {
      "file": "bundles/hermes/items.py",
      "line": 49,
      "severity": "major",
      "message": "Should Fix: When hermes.enabled=False but matrix.enabled=True (or brave.enabled=True) in node config, the validation at line 51 still fires and raises 'hermes.matrix.* required'. Disabling hermes should skip sub-config validation entirely — consider wrapping the validation block with an additional `hermes.get('enabled', False)` check, or clarify this is intentional."
    },
    {
      "file": "bundles/hermes/items.py",
      "line": 68,
      "severity": "major",
      "message": "Should Fix: The hermes_dir (~/.hermes/) directory is created when env_vars is non-empty (line 69) but is never deleted when env_vars becomes empty (the else branch at line 93 only sets credentials_file to delete:True). The parent directory persists indefinitely after credentials are removed. Consider adding hermes_dir to the files dict with delete:True in the else branch."
    },
    {
      "file": "nodes/hz.hetzner-2.py",
      "line": 35,
      "severity": "major",
      "message": "Should Fix: hermes.matrix is enabled with TeamVault key VO053L, but the PR description states Matrix integration is deferred and the key may not exist. Confirm VO053L exists in TeamVault before merging — otherwise 'bw apply' will fail at runtime when resolving teamvault.username('VO053L', ...)."
    },
    {
      "file": "bundles/hermes/metadata.py",
      "line": 27,
      "severity": "nit",
      "message": "Nice to Have: The add_user reactor unconditionally provides 'users.hermes.enabled': True when hermes.enabled=True, silently overwriting any explicit 'users.hermes' entry in node config. This is documented in the README but could cause surprise. Consider merging with existing node config rather than replacing it, or add a comment emphasizing this override behavior."
    },
    {
      "file": "bundles/hermes/items.py",
      "line": 51,
      "severity": "nit",
      "message": "Nice to Have: The openclaw bundle made the same change (is None → not matrix.get(field)). This is correct — empty strings for homeserver/user_id/password are never valid. No action needed, just confirming alignment."
    }
  ],
  "concerns_addressed": [
    "correctness: openclaw items.py 'is None' → 'not matrix.get()' change is correct improvement — empty strings for homeserver/user_id/password are never valid credentials",
    "correctness: hermes_dir directory (~/.hermes/) not cleaned up when credentials removed — raised as Should Fix",
    "correctness: hermes metadata.py reactor overwrites explicit users.hermes settings — documented behavior, raised as Nice to Have",
    "correctness: openclaw users.openclaw removed from hz.hetzner-2.py, relying on reactor — works correctly with reactor ordering",
    "correctness: VO053L TeamVault key referenced in hz.hetzner-2.py for hermes.matrix — confirmed as Should Fix concern, key existence must be verified before merge",
    "security: hermes.service EnvironmentFile=- prefix correct, binary path fail-loop documented in README — working as designed"
  ]
}

The earlier change `is None` → `not matrix.get(field)` (per bot review
suggestion) breaks CI: `not Fault` invokes `__len__` which forces
TeamVault resolution, which fails on the GitHub runner because it
has no /home/runner/.bw_teamvault_secrets.cfg.

`is None` is the correct check — Fault objects pass through the
identity check without triggering resolution. The "empty string"
concern that motivated the change doesn't materialize in practice
(TeamVault doesn't return empty strings, and a non-empty Fault
container is not empty even before resolution).

Comment inline in both bundles so the next reviewer doesn't suggest
the same "fix".
@bborbe

bborbe commented Jun 4, 2026

Copy link
Copy Markdown
Owner Author

Triage of latest review (CHANGES_REQUESTED on `64035fd`):

CI failure (test): ✅ fixed in `de6f224`. Root cause: the earlier `is None` → `not matrix.get(field)` change (which the bot itself recommended on PR #18) breaks CI because `not Fault` invokes `len` which forces TeamVault resolution, and the GitHub runner has no `/home/runner/.bw_teamvault_secrets.cfg`. Reverted with an inline comment in both bundles so the next reviewer doesn't suggest the same "fix".

Major #1 — validation fires when hermes.enabled=False but matrix.enabled=True: ❌ misread. The validation is inside `if hermes.get('enabled', False) and matrix.get('enabled', False):` — both must be True. With `hermes.enabled=False` the entire block is skipped.

Major #2 — hermes_dir not cleaned up when credentials removed: ❌ by design. `~/.hermes/` is Hermes-owned (config, runtime state, room IDs, memories). The README's "Credentials → bw-credentials.env, not ~/.hermes/.env" section spells out exactly why bw must NOT manage that directory's lifecycle. Deleting it would wipe Hermes state on every credential rotation.

Major #3 — VO053L might not exist: ✅ verified. `bw apply hz.hetzner-2` ran successfully end-to-end and the resulting `bw-credentials.env` was loaded by the systemd unit; the gateway connected to Matrix and is currently DMing as `@openhermes:matrix.benjamin-borbe.de`. The key exists.

Nit #1 — reactor overwrite of users.hermes: bundlewrap metadata reactors merge (not replace) — explicit node config still wins for unset keys. The reactor only injects `enabled: True`. No surprise risk.

Nit #2 — alignment confirm: no action.

- hermes.brave.enabled → BRAVE_SEARCH_API_KEY from TeamVault.
- hermes.telegram.enabled → TELEGRAM_BOT_TOKEN + TELEGRAM_ALLOWED_USERS.
  Allowlist is required by the bundle (set "" to opt into open access)
  because Telegram's public bot API lets anyone with the bot username
  DM and command it; locked-down-by-default matches MATRIX_ALLOWED_USERS
  posture. Numeric Telegram user IDs preferred over @usernames — IDs
  survive handle changes.
- hz.hetzner-2 enables both: brave via TeamVault dwkkzw, telegram via
  XO7Qxq + allowlist 112230768 (@bborbe).
@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot dismissed their stale review June 4, 2026 23:26

superseded by new automated review

@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I have all the information needed for the review. Let me compile the findings:

Review Summary:

The hermes bundle is a well-designed addition that mirrors the openclaw pattern. All concerns from the plan are addressed:

  • ✅ Reactor ordering is correct (add_user provides users before consumers)
  • is None pattern applied consistently to both hermes and openclaw
  • ✅ Manual users.openclaw entry removed from hz.hetzner-2.py; no conflicting entries elsewhere
  • ✅ MATRIX_USER vs MATRIX_USER_ID is intentional per comments (different agents)
  • ✅ README documents the binary-not-managed-by-bw limitation

Key findings:

  1. EnvironmentFile=- silently ignores missing credentials (intentional but risky)
  2. No pre-check that hermes binary exists before enabling systemd service
  3. No hermes-specific test coverage added
{
  "verdict": "approve",
  "summary": "The hermes bundle is a well-structured addition that correctly mirrors the openclaw bundle pattern. Reactor ordering is proper, the 'is None' TeamVault Fault pattern is consistently applied, manual user entries are correctly removed in favor of metadata reactors, and the intentional MATRIX_USER vs MATRIX_USER_ID naming difference is properly documented. Three optional improvements identified: silent EnvironmentFile failure mode, missing binary existence guard, and lack of hermes-specific test coverage.",
  "comments": [
    {
      "file": "bundles/hermes/files/hermes.service",
      "line": 14,
      "severity": "major",
      "message": "[Nice to Have] EnvironmentFile=- silently ignores missing credentials file. If TeamVault keys are absent/misconfigured, gateway starts without auth. This is documented as intentional, but could cause silent auth failures in production. Consider failing fast if credentials are expected but file is missing."
    },
    {
      "file": "bundles/hermes/items.py",
      "line": 1,
      "severity": "major",
      "message": "[Nice to Have] No pre-check that hermes binary exists before enabling the systemd service. The service will fail-loop with unclear errors if the upstream installer hasn't been run. A guard like `test -x /home/hermes/.local/bin/hermes` or documenting a required check would improve UX."
    },
    {
      "file": "bundles/hermes/",
      "line": 1,
      "severity": "major",
      "message": "[Nice to Have] No hermes-specific tests added. The PR notes 'bw test -i' passed (6683 vs 6635 delta) but it's unclear if hermes bundle logic is specifically tested. Consider adding test coverage for the new bundle."
    }
  ],
  "concerns_addressed": [
    "correctness: hermes add_user reactor provides 'users' before apt packages reactor - ordering verified correct in bundles/hermes/metadata.py:19-33",
    "correctness: openclaw add_user reactor added in bundles/openclaw/metadata.py:15-29; manual entry removed from nodes/hz.hetzner-2.py - verified no other nodes have conflicting entries",
    "correctness: 'is None' pattern consistently applied in both hermes (items.py:54) and openclaw (items.py:16) - no 'not' pattern found",
    "correctness: nodes/hz.hetzner-2.py manual 'users.openclaw' entry removed - grep confirmed no other nodes reference openclaw/hermes users manually",
    "security: EnvironmentFile=- pattern intentional per comment but noted as silent failure risk",
    "correctness: MATRIX_USER vs MATRIX_USER_ID intentional per explicit comment in hermes/items.py:59-60 - not a bug"
  ]
}

@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot dismissed their stale review June 4, 2026 23:31

superseded by new automated review

@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on my comprehensive review of the diff, here is my analysis:

PR Review Summary

Verdict: request-changes

Must Fix (Critical)

  1. hermes.service missing systemd security hardening - PrivateTmp=, NoNewPrivileges=, ReadOnlyFilesystem=, ProtectSystem=, ProtectHome=, PrivateDevices=, CapabilityBoundingSet= are all absent. Credentials file in /tmp can be exposed. The openclaw bundle likely has the same issue on master.

  2. TimeoutStopSec=210s too tight - drain_timeout=180s per bundle docs, only 30s buffer before SIGKILL. If drain takes longer under load, mid-drain SIGKILL causes service disruption. Should be 240-300s.

  3. Telegram allowed_users raw numeric ID in git - 'allowed_users': '112230768' exposes the Telegram user ID in source code. Anyone with that ID can interact with the bot. Should use TeamVault reference.

  4. Unnecessary Playwright Chromium libs - hermes is a CLI agent, not browser automation. 30+ heavy libs (libasound2t64, libatk-bridge2.0-0t64, libcairo2, libcups2t64, libdrm2, libgbm1, libglib2.0-0t64, libnss3, libpango-1.0-0, libx11-6, etc.) bloat every node with hermes enabled.

Should Fix (Important)

  1. Matrix deferred but enabled in same commit - PR claims "Matrix integration... deferred to follow-up PR" yet matrix.enabled=True is set. TeamVault key VO053L may not exist.

  2. Race condition in add_user reactors - Both hermes and openclaw add_user reactors write to users dict. If both bundles are enabled on same node simultaneously, could have concurrent writes.

Nice to Have (Optional)

  1. No LICENSE file found in repo root.

{
  "verdict": "request-changes",
  "summary": "The hermes bundle introduces a CLI agent provisioning system with systemd, TeamVault credential injection, and metadata reactors. Critical issues include missing systemd security hardening (PrivateTmp, NoNewPrivileges, ReadOnlyFilesystem), an undersized TimeoutStopSec (210s vs 180s drain), raw Telegram user ID in source code, and unnecessary Playwright Chromium system libraries for a CLI-only agent. The PR also claims Matrix is deferred yet enables it with potentially unprovisioned TeamVault keys.",
  "comments": [
    {
      "file": "bundles/hermes/files/hermes.service",
      "line": 1,
      "severity": "critical",
      "message": "Missing systemd security hardening: PrivateTmp=, NoNewPrivileges=, ReadOnlyFilesystem=, ProtectSystem=, ProtectHome=, PrivateDevices=, CapabilityBoundingSet= are absent. Credentials file in /tmp can be exposed to other services."
    },
    {
      "file": "bundles/hermes/files/hermes.service",
      "line": 17,
      "severity": "critical",
      "message": "TimeoutStopSec=210s is too tight. Hermes drain_timeout=180s per bundle docs, leaving only 30s buffer before SIGKILL mid-drain. Should be 240-300s for safe margin under load."
    },
    {
      "file": "nodes/hz.hetzner-2.py",
      "line": 42,
      "severity": "critical",
      "message": "Telegram allowed_users raw numeric ID '112230768' in source code. Anyone with this Telegram ID can interact with the bot. Should use TeamVault reference to avoid exposure in git history."
    },
    {
      "file": "bundles/hermes/metadata.py",
      "line": 45,
      "severity": "critical",
      "message": "Playwright Chromium system libs unnecessary for hermes CLI agent (libasound2t64, libatk-bridge2.0-0t64, libcairo2, libcups2t64, libdrm2, libgbm1, libglib2.0-0t64, libnss3, libpango-1.0-0, libx11-6, etc.). These 30+ packages add significant bloat. Remove unless hermes actually uses Playwright/Chromium."
    },
    {
      "file": "nodes/hz.hetzner-2.py",
      "line": 30,
      "severity": "major",
      "message": "PR claims 'Matrix integration... deferred to follow-up PR' yet matrix.enabled=True is set with TeamVault key VO053L. That key may not exist or be a placeholder, causing hermes to fail-start."
    },
    {
      "file": "bundles/hermes/metadata.py",
      "line": 24,
      "severity": "major",
      "message": "Race condition: both hermes and openclaw add_user reactors write to users dict. If both bundles enabled on same node, concurrent reactor execution could cause conflicts."
    }
  ],
  "concerns_addressed": [
    "security: hermes.service lacks PrivateTmp= — credentials in /tmp exposed (raised as critical)",
    "security: Telegram allowed_users raw ID 112230768 in git — should be TeamVault (raised as critical)",
    "security: TeamVault refs for matrix/brave credentials used correctly in hz.hetzner-2.py",
    "correctness: TimeoutStopSec=210s too short for 180s drain_timeout — raised as critical, suggest 240-300s",
    "correctness: add_user reactor race condition between hermes + openclaw — raised as major",
    "correctness: Playwright Chromium libs unnecessary for CLI hermes — raised as critical",
    "correctness: Matrix deferred but enabled with potentially unprovisioned TeamVault key — raised as major"
  ]
}

bborbe added 2 commits June 5, 2026 01:41
Upstream installer renames hermes.service → hermes-gateway.service and
treats any /etc/systemd/system/hermes*.service as 'legacy' (prints
migrate-legacy warning on every update). The Hermes CLI
('hermes gateway restart/stop/run') targets the user-level unit, so a
system-level unit fights both the CLI and the upstream tooling.

Switch model:
- bw no longer writes /etc/systemd/system/hermes.service. Cleanup stub
  stays unconditionally to remove the file/svc that earlier commits
  installed.
- bw runs 'loginctl enable-linger hermes' so the user-level systemd unit
  (installed by the upstream installer at
  ~/.config/systemd/user/hermes-gateway.service) survives logout and
  starts at boot without anyone logging in. Idempotent action.

README rewritten accordingly. Credentials file mechanism (separate
bw-credentials.env loaded via EnvironmentFile= in the user-level unit)
still works — Hermes's user unit can reference the same file path.
Upstream's ~/.config/systemd/user/hermes-gateway.service has NO
EnvironmentFile= directive, so bw-credentials.env was being written
but never loaded. Hermes itself reads ~/.hermes/.env via python-dotenv,
which means TeamVault rotation never flowed to the running gateway.

Fix: bw writes a systemd drop-in at
~/.config/systemd/user/hermes-gateway.service.d/bw-credentials.conf
adding the EnvironmentFile= line. Systemd-loaded values take precedence
over python-dotenv reads — TeamVault wins.

Drop-ins are additive and survive Hermes updates that regenerate the
main unit (only the .d/ folder is admin territory). bw also runs
user-systemctl via 'sudo -u hermes XDG_RUNTIME_DIR=/run/user/<uid>'
on changes to either the drop-in or the credentials file, so apply
triggers daemon-reload + restart hermes-gateway without manual SSH.

Bug fix in the same commit: the credentials-file delete branch was
unconditionally triggering the reload action, which only exists when
hermes.enabled=True. On nodes without hermes that broke metadata
resolution. Trigger now conditional.
@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot dismissed their stale review June 4, 2026 23:46

superseded by new automated review

@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on my analysis, I have enough information to provide a comprehensive review. Let me compile the findings:

PR Review Summary

Reviewing: bborbe/bw PR #19 - hermes: new bundle mirroring openclaw + auto-inject user
Base: master → Head: feature/hermes-user-bundle (760a6bd)

Key Findings

Must Fix:

  1. bundles/hermes/items.py:42-45 - svc_systemd['hermes'] with running: False and enabled: False is set unconditionally, outside the if hermes.get('enabled', False) block (line 27). This means bw will attempt to stop the hermes system-level unit on every apply, even when hermes.enabled = True. This conflicts with Hermes's own user-level systemd unit management and could cause Hermes to be repeatedly stopped after users start it manually.

Should Fix:

  1. bundles/openclaw/items.py:16 - The is None check for matrix.get(field) correctly handles bw Fault/TeamVault resolution. However, this pattern was only added for openclaw's matrix fields. Unlike hermes (which has additional brave and telegram credential blocks with their own is None checks), openclaw only has matrix. The implementation is correct but the comment block explaining the is None pattern (lines 13-15) is now duplicated from hermes — consider extracting to a shared constant or comment.

Correctly Implemented (no issues):

  • bundles/hermes/items.py:101 - credentials file mode 0600 correctly enforced
  • bundles/hermes/items.py:74,78 - telegram bot_token and allowed_users is None checks with required allowlist policy
  • bundles/hermes/metadata.py:29 - user auto-injection correctly gated on hermes.get('enabled', False)
  • bundles/openclaw/metadata.py:20 - openclaw user auto-injection correctly gated
  • bundles/openclaw/items.py:16 and bundles/hermes/items.py:54,66,74,78 - consistent is None pattern for TeamVault/Fault resolution across both bundles
  • groups/meta/bundles.py - hermes correctly added to bundles list
  • groups/meta/user.py:93-96 - hermes user (uid 11021) correctly defined
  • nodes/hz.hetzner-2.py - users.openclaw removed, hermes config added with all credential blocks
  • ✅ Legacy service cleanup via files['/etc/systemd/system/hermes.service'] = {'delete': True} is appropriate

Nice to Have:

  • bundles/hermes/README.md is comprehensive and well-documented
  • The env mako template (bundles/hermes/files/env) is simple and correct

{
  "verdict": "request-changes",
  "summary": "The hermes bundle is well-structured and largely correct, but the unconditional svc_systemd stop at lines 42-45 of bundles/hermes/items.py will cause Hermes to be stopped on every bw apply even when hermes.enabled=True, conflicting with Hermes's self-managed user-level systemd unit. This must be gated on hermes.get('enabled', False).",
  "comments": [
    {
      "file": "bundles/hermes/items.py",
      "line": 42,
      "severity": "critical",
      "message": "[security/correctness] svc_systemd['hermes'] with running:False and enabled:False is set unconditionally (outside the if hermes.get('enabled', False) block at line 27). This will stop hermes.service on every bw apply, even when hermes.enabled=True. The legacy cleanup comment (line 36-38) explains the intent, but the unconditional stop conflicts with Hermes managing its own user-level systemd unit. Gate this on hermes.get('enabled', False) or move it inside the existing if block."
    },
    {
      "file": "bundles/hermes/items.py",
      "line": 36,
      "severity": "major",
      "message": "[correctness] The comment says 'Stays on the disabled branch unconditionally so the cleanup happens even if hermes is later disabled' — but svc_systemd['hermes'] also sets running:False unconditionally. If hermes is later re-enabled, the user would start it via 'hermes gateway start' but the next bw apply would stop it again. Consider whether the svc_systemd enforcement should also be conditional on hermes.enabled (e.g., only enforce running/enabled=False when hermes is NOT enabled)."
    }
  ],
  "concerns_addressed": [
    "security: credentials file mode 0600 enforced at items.py:101 — addressed",
    "security: telegram bot_token + allowed_users allowlist checked with is None at items.py:74,78 — addressed",
    "security: auto-inject user reactor gated on hermes.enabled at metadata.py:29 — addressed",
    "correctness: is None check for bw Fault consistent between hermes and openclaw — addressed",
    "correctness: legacy hermes.service cleanup via file:delete=True at items.py:39 — addressed, but svc_systemd stop is unconditional (see critical finding)",
    "correctness: openclaw add_user reactor added at metadata.py:18-29 — addressed",
    "correctness: users.openclaw removed from hz.hetzner-2.py — addressed"
  ]
}

@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot dismissed their stale review June 4, 2026 23:51

superseded by new automated review

@ben-s-pull-request-reviewer ben-s-pull-request-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
  "verdict": "approve",
  "summary": "PR adds a new hermes bundle that mirrors the openclaw structure with user auto-injection via metadata reactor, systemd user-level lifecycle management, and TeamVault-backed credential injection. All security-sensitive files use template rendering with no hardcoded secrets. The openclaw auto-inject pattern is consistent and doesn't regress on other nodes (hz.hetzner-1 has no openclaw/hermes). No issues found.",
  "comments": [],
  "concerns_addressed": [
    "security: bundles/hermes/files/bw-credentials.conf - template-only, no hardcoded secrets, uses EnvironmentFile=- for safe missing-file handling",
    "security: bundles/hermes/files/env - mako template renders env vars from context, no plaintext credentials committed",
    "security: bundles/openclaw/metadata.py - add_user reactor only sets enabled=True, cannot inject arbitrary users",
    "correctness: bundles/openclaw/metadata.py - hz.hetzner-1.py has no openclaw/hermes, no regression from auto-inject change",
    "correctness: bundles/hermes/metadata.py - structure mirrors openclaw with consistent reactor pattern, apt packages include pipx and playwright deps",
    "correctness: nodes/hz.hetzner-2.py - manual users.openclaw entry removed, user now auto-injected via openclaw add_user reactor; no other code paths reference the old manual entry",
    "tests: groups/meta/bundles.py - hermes correctly registered at line 28, alphabetical order maintained",
    "tests: groups/meta/user.py - hermes uid/gid 11021 added, no collision with existing users (11000-11020 occupied)"
  ]
}

@bborbe bborbe merged commit 01560f8 into master Jun 5, 2026
3 checks passed
@bborbe bborbe deleted the feature/hermes-user-bundle branch June 5, 2026 07:49
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