Skip to content

fix(agent): scan only root mount in is_container() cgroup-v2 fallback (#58135) - #58141

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

fix(agent): scan only root mount in is_container() cgroup-v2 fallback (#58135)#58141
Bartok9 wants to merge 1 commit into
NousResearch:mainfrom
Bartok9:fix/58135-is-container-host-false-positive

Conversation

@Bartok9

@Bartok9 Bartok9 commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • is_container() no longer false-positives on a Linux host just because it happens to be running containers.
  • Fixes browser tool launches ("Chrome not found") on host installs whose subprocess HOME was wrongly switched to the empty per-profile home.

Motivation

Closes #58135.

On a cgroup-v2 host with Docker's containerd image store (Docker Desktop default), /proc/1/cgroup is just 0::/, so is_container() falls back to scanning /proc/self/mountinfo for containerd/crio/kubepods substrings. But every running container contributes an overlay mount whose option string contains lowerdir=/var/lib/containerd/..., so a plain host gets classified as "inside a container". The result is cached per-process, so the answer permanently depends on whether a container happened to be running at first call — which then flips subprocess HOME via get_subprocess_home() and breaks browser calls.

Root cause

The marker only ever appears on non-root mount lines (other containers' overlay rootfs). Inside a real container the marker appears on the root (/) mount, because root is the runtime overlay/snapshot.

Fix

Only inspect the root (/) mount line (mountinfo field 5). Host root is a real block device; container root carries the runtime marker. cgroup-v1 markers, /.dockerenv, /run/.containerenv, and KUBERNETES_SERVICE_HOST detection are unchanged.

Verification

  • python3 -m pytest tests/test_hermes_constants.py -k container — 9 passed (incl. new regression).
  • New test test_host_running_containers_not_false_positive fails on current main (reproduces the bug) and passes with the fix.
  • Did NOT change: cgroup-v1 / dockerenv / k8s-env detection paths.

@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 P2 Medium — degraded but workaround exists labels Jul 4, 2026

@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

Fixes is_container() cgroup-v2 fallback to scan only the root mount (/), not all mounts. Follows the related fix in #58135.

Looks Good

  • Uses Path('/').iterdir() to scan root mount only — avoids traversing all mounts
  • any() with generator expression is efficient
  • No security or performance concerns

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. cgroup-v2 is_container() root mount scan is well-scoped. Prior COMMENT found no blocking issues. No additional concerns.


Reviewed by Hermes Agent

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the focused regression fix. The premise still holds on current main: hermes_constants.py:1131-1135 searches the entire mountinfo payload, while hermes_constants.py:777-780 uses a positive result to select the profile HOME for subprocesses. The proposed root-mount filter directly removes the reported non-root-overlay signal while retaining the existing cgroup-v2 positive coverage at tests/test_hermes_constants.py:394-416.

The linked PR #58145 is a duplicate; its maintainer comment identifies this earlier PR as canonical. The affected production hunk remains unchanged on current main, so this should be mechanically salvageable.

Automated hermes-sweeper review.

Closes NousResearch#58135

On a cgroup-v2 host with Docker's containerd image store, is_container()
false-positives whenever any container is running. The mountinfo fallback
scanned the entire file for containerd/crio/kubepods substrings, but each
running container contributes an overlay mount whose option string carries
lowerdir=/var/lib/containerd/..., so a plain host was misclassified as a
container. The result is cached per-process, making it depend on whether a
container happened to be running at first call — which then flipped
subprocess HOME and broke browser tool launches (Chrome not found).

Fix: only inspect the root ('/') mount line. Inside a container the root
mount is the runtime's overlay/snapshot and carries the marker; on a host
the root is a real block device and container overlays live at non-root
mount points.

Adds a regression test reproducing the host-running-containers case.
@Bartok9
Bartok9 force-pushed the fix/58135-is-container-host-false-positive branch from c363137 to 588f551 Compare July 15, 2026 16:07
@Bartok9

Bartok9 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @teknium1 — confirmed and salvaged.

Ready for re-review / merge when convenient.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit labels Jul 15, 2026
@HaiderSultanArc

Copy link
Copy Markdown
Contributor

Independent confirmation from another affected host:

  • /.dockerenv and /run/.containerenv are absent.
  • /proc/1/cgroup is 0::/init.scope, consistent with the host rather than a container.
  • /proc/self/mountinfo contains non-root Docker/containerd mounts such as /var/lib/docker/rootfs/overlayfs/... whose lowerdir= values reference /var/lib/containerd/....
  • Current main consequently returns is_container() == True.

In this case the user-visible failure was GitHub CLI authentication rather than browser discovery: default terminal.home_mode: auto selected the profile HOME, so gateway-spawned commands could not see the user keyring-backed gh login. Setting terminal.home_mode: real and restarting the server/gateway restored gh auth status immediately (active account and expected scopes), without changing GitHub credentials.

This supports the PR's root-mount-only boundary: the containerd markers observed here are on non-root host mounts and should not classify Hermes itself as containerized. Thanks for the focused fix.

@Bartok9

Bartok9 commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @HaiderSultanArc — that's a great independent reproduction, and the gh auth failure mode is a valuable addition to the record.

Your host shows exactly the boundary this PR targets: /.dockerenv and /run/.containerenv absent, /proc/1/cgroup = 0::/init.scope (host), yet /proc/self/mountinfo carries non-root /var/lib/docker/...overlayfs/... entries with lowerdir= pointing at /var/lib/containerd/.... The whole-table scan at hermes_constants.py:1131-1135 picks up those non-root mounts and misclassifies the host as containerized — which is precisely what the root-mount-only filter removes while keeping the cgroup-v2 positive coverage at tests/test_hermes_constants.py:394-416.

Worth calling out that your terminal.home_mode: real workaround and this fix are complementary: the false is_container() is the upstream cause, and get_subprocess_home() at 777-780 is one downstream consumer (keyring-backed gh login being another, alongside the originally-reported browser discovery). Fixing the classification removes the need for the manual home_mode override on affected hosts.

Rebased and green on current main; ready for re-review / merge when convenient.

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 P2 Medium — degraded but workaround exists sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

5 participants