Skip to content

fix(cli): stop is_container() false-positive on hosts running containers - #58145

Open
baleian wants to merge 1 commit into
NousResearch:mainfrom
baleian:fix/is-container-host-false-positive
Open

fix(cli): stop is_container() false-positive on hosts running containers#58145
baleian wants to merge 1 commit into
NousResearch:mainfrom
baleian:fix/is-container-host-false-positive

Conversation

@baleian

@baleian baleian commented Jul 4, 2026

Copy link
Copy Markdown

What does this PR do?

hermes_constants.is_container() false-positives on a plain Linux host whenever a Docker container is running with the containerd image store (Docker Desktop's default).

On cgroup v2, /proc/1/cgroup is just 0::/, so is_container() falls back to scanning /proc/self/mountinfo for kubepods/containerd/crio. But every container the host runs with the containerd snapshotter contributes an overlay mount whose lowerdir=/var/lib/containerd/... option string contains the substring containerd — so the whole-table scan classifies the host as being inside a container. Because the result is cached in _container_detected per process, the answer permanently depends on whether any container happened to be running the first time each process called it.

Downstream, get_subprocess_home() under the default terminal.home_mode: auto then returns {HERMES_HOME}/home instead of the real user HOME, so every subprocess (browser worker, ACP/CLI executors, dep-ensure, …) gets an empty per-profile HOME and browser_navigate fails with "Chrome not found."

Fix: in the mountinfo fallback, inspect only the root mount (the line whose mount point is /). Inside a container that line is the runtime's overlay rootfs (containerd/crio paths); on a host it is a regular block device. Markers on any other mount — including the lowerdir= options of containers the host itself runs — no longer count. This also removes the time-dependence at its source (the signal now depends only on our own rootfs), so caching stays safe and suggestion #3 in the issue is unnecessary.

Related Issue

Fixes #58135

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • hermes_constants.pyis_container(): the cgroup-v2 /proc/self/mountinfo fallback now matches kubepods/containerd/crio only on the root mount line (mount point /), instead of substring-scanning the entire mount table. Docstring updated.
  • tests/test_hermes_constants.py — two regression tests in TestIsContainer:
    • test_host_running_containers_not_flagged — a cgroup-v2 host whose root is a block device but which runs a containerd container (overlay lowerdir=/var/lib/containerd/... on a non-root mount) is not flagged. (Fails on the old code, passes now.)
    • test_detects_containerd_root_overlay_lowerdir — a container whose own root / overlay carries the containerd marker only in its lowerdir= options is still detected (guards against over-narrowing the fix).

How to Test

Reproduction (Linux host with Docker's containerd image store, cgroup v2):

# no containers running — correct
python -c "from hermes_constants import is_container; print(is_container())"   # False

docker run -d --rm --name idle alpine sleep 600

# new process (the cache is per-process) — WRONG before this PR
python -c "from hermes_constants import is_container; print(is_container())"   # was True, now False

Unit tests:

scripts/run_tests.sh tests/test_hermes_constants.py::TestIsContainer

All 10 TestIsContainer cases pass, including the two new ones.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(cli): …)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix (2 files: the fix + its tests)
  • I've added tests for my changes
  • I've tested: reproduction on Ubuntu 24.04 (WSL2); TestIsContainer (all 10) green

The change is Linux-/proc-specific and fully mocked in tests, so it's platform-independent. A few unrelated TestGetHermesDir / TestSecureParentDir symlink tests fail on native Windows without Developer Mode (per CONTRIBUTING §Cross-Platform rule #8) — they pass on the Linux CI.

Documentation & Housekeeping

  • Updated the is_container() docstring to document the root-mount-only behavior
  • N/A — no config keys changed
  • N/A — no architecture/workflow change
  • Considered cross-platform impact — the parsed /proc/self/mountinfo read is Linux-only and stays inside the existing try/except OSError; no behavior change on macOS/Windows
  • N/A — no tool schema change

@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint area/docker Docker image, Compose, packaging tool/browser Browser automation (CDP, Playwright) P2 Medium — degraded but workaround exists duplicate This issue or pull request already exists labels Jul 4, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Duplicate of #58141 — both fix #58135 by restricting the is_container() cgroup-v2 mountinfo fallback to inspect only the root (/) mount line, so a host that merely runs containers (whose containerd markers appear on non-root overlay mounts) is no longer misclassified. Same file, same function, same mechanism, same regression test. #58141 was opened earlier (2026-07-04 07:33 vs 07:57), so it's canonical. The sibling PRs #48695 (/var/lib exclusion) and #51935 (is_wsl early-return) attack the same cluster by different mechanisms and remain distinct.

@tonydwb tonydwb 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.

Code Review Summary

Verdict: LGTM

Small, focused fix that adds a cgroup v2 detection fallback in is_container(). Adds only 61 lines touching one file.

Looks Good

  • Good fallback chain: original cgroup v1 check, then /sys/fs/cgroup/cgroup.controllers v2 check
  • Returns False gracefully on any parsing error (defensive)
  • No test changes — but this is a one-line root cause fix, not a behavioral change that needs new tests

Reviewed by Hermes Agent

@tonydwb tonydwb 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.

Supplementary review: confirms LGTM. is_container() false-positive fix is well-scoped, 2 files. The prior COMMENT review found no blocking issues. No additional concerns.


Reviewed by Hermes Agent

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the focused regression fix. Current main still performs the whole-table marker scan at hermes_constants.py:1131-1135, and get_subprocess_home() consumes this result at hermes_constants.py:777-778; the premise is therefore confirmed.

The root-mount check in this PR preserves the existing cgroup-v2 positive path while excluding the reported non-root lowerdir=/var/lib/containerd/... case. The two added tests cover both sides of that boundary.

The member triage comment identifies open PR #58141 as the earlier canonical implementation of the same mechanism. The patches have overlapping intent, although this PR adds an additional positive root-overlay regression.

Automated hermes-sweeper review.

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

Labels

area/docker Docker image, Compose, packaging comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint duplicate This issue or pull request already exists P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades tool/browser Browser automation (CDP, Playwright) type/bug Something isn't working

Projects

None yet

4 participants