Skip to content

test(tools): add unit tests for process_registry.py - #141

Closed
Bartok9 wants to merge 1 commit into
NousResearch:mainfrom
Bartok9:add-process-registry-tests
Closed

test(tools): add unit tests for process_registry.py#141
Bartok9 wants to merge 1 commit into
NousResearch:mainfrom
Bartok9:add-process-registry-tests

Conversation

@Bartok9

@Bartok9 Bartok9 commented Feb 27, 2026

Copy link
Copy Markdown
Contributor

Adds 16 comprehensive tests for the process registry module.

Coverage

ProcessSession

  • Initialization with defaults
  • Initialization with custom values
  • Thread-safe lock creation

ProcessRegistry

  • ID generation format and uniqueness
  • Get nonexistent session
  • List empty/populated registries
  • List filtering by task_id
  • Poll running sessions
  • Poll exited sessions
  • Kill nonexistent sessions
  • Output buffer truncation

Constants

  • MAX_OUTPUT_CHARS value
  • FINISHED_TTL_SECONDS value
  • MAX_PROCESSES limit

Why

Improves test coverage for the background process management system which is critical for terminal(background=true) operations.

Adds 16 tests covering:
- ProcessSession dataclass initialization
- ProcessSession defaults and custom values
- Thread-safe locking
- ProcessRegistry ID generation
- Session listing and filtering
- Poll status for running/exited processes
- Kill operation error handling
- Output buffer rolling window truncation
- Module constants validation

Improves test coverage for the background process management system.
@teknium1 teknium1 closed this Mar 1, 2026
teknium1 pushed a commit that referenced this pull request Jun 28, 2026
…uded gui_bodies.log

Stacked on #49003. That PR added always-on metadata (method/path/status/
latency + WS lifecycle) to the gui surface. This adds the heavy diagnostic
tier — actual HTTP request bodies and PTY/WebSocket frames — for the hard
dashboard/TUI bugs where metadata alone isn't enough.

Body content can carry conversation data, so this is opt-in and built to be
structurally incapable of leaking into a shared debug report (see #22016):

- New config logging.capture_bodies (default false), surfaced in the dashboard
  / hermes tools config UI via _SCHEMA_OVERRIDES with a warning description.
- When enabled, bodies go to a SEPARATE gui_bodies.log written by a dedicated
  logger (hermes_body_capture, propagate=False) that is deliberately NOT a
  member of any COMPONENT_PREFIXES. Four structural guarantees, all tested:
    1. not under any component prefix  -> never lands in gui.log / agent.log
    2. not in hermes_cli/logs.py LOG_FILES -> not tailable via --- ~/.hermes/logs/agent.log (last 50) ---
2026-06-19 18:48:30,316 INFO [20260619_173001_f45949] agent.conversation_loop: API call #4: model=anthropic/claude-opus-4.8 provider=openrouter in=288583 out=529 total=289112 latency=10.6s cache=284912/288583 (99%)
2026-06-19 18:48:30,318 INFO [20260619_173001_f45949] agent.conversation_loop: Turn ended: reason=text_response(finish_reason=stop) model=anthropic/claude-opus-4.8 api_calls=4/16 budget=4/16 tool_turns=110 last_msg_role=assistant response_len=1474 session=20260619_173001_f45949
2026-06-19 18:48:30,325 INFO [20260619_173001_f45949] run_agent: OpenAI client closed (agent_close, shared=True, tcp_force_closed=0) thread=bg-review:6349795328 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:48:30,652 INFO run_agent: OpenAI client closed (stream_request_complete, shared=False, tcp_force_closed=0) thread=Thread-747 (_call):6421311488 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:48:30,653 INFO [20260619_153431_51fd01] agent.conversation_loop: API call #139: model=anthropic/claude-opus-4.8 provider=openrouter in=243960 out=991 total=244951 latency=11.5s cache=242196/243960 (99%)
2026-06-19 18:48:31,348 INFO [20260619_153431_51fd01] agent.tool_executor: tool terminal completed (0.69s, 161 chars)
2026-06-19 18:48:31,384 INFO run_agent: OpenAI client created (chat_completion_stream_request, shared=False) thread=Thread-749 (_call):6421311488 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:48:51,510 INFO run_agent: OpenAI client closed (stream_request_complete, shared=False, tcp_force_closed=0) thread=Thread-749 (_call):6421311488 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:48:51,511 INFO [20260619_153431_51fd01] agent.conversation_loop: API call #140: model=anthropic/claude-opus-4.8 provider=openrouter in=245038 out=1783 total=246821 latency=20.1s cache=243477/245038 (99%)
2026-06-19 18:48:52,215 INFO [20260619_153431_51fd01] agent.tool_executor: tool terminal completed (0.70s, 153 chars)
2026-06-19 18:48:52,245 INFO run_agent: OpenAI client created (chat_completion_stream_request, shared=False) thread=Thread-751 (_call):6421311488 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:48:59,489 INFO run_agent: OpenAI client closed (stream_request_complete, shared=False, tcp_force_closed=0) thread=Thread-751 (_call):6421311488 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:48:59,490 INFO [20260619_153431_51fd01] agent.conversation_loop: API call #141: model=anthropic/claude-opus-4.8 provider=openrouter in=246873 out=493 total=247366 latency=7.3s cache=244127/246873 (99%)
2026-06-19 18:49:13,666 INFO [20260619_153431_51fd01] agent.tool_executor: tool terminal completed (14.17s, 979 chars)
2026-06-19 18:49:13,692 INFO run_agent: OpenAI client created (chat_completion_stream_request, shared=False) thread=Thread-753 (_call):6421311488 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:49:22,930 INFO run_agent: OpenAI client closed (stream_request_complete, shared=False, tcp_force_closed=0) thread=Thread-753 (_call):6421311488 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:49:22,932 INFO [20260619_153431_51fd01] agent.conversation_loop: API call #142: model=anthropic/claude-opus-4.8 provider=openrouter in=247686 out=548 total=248234 latency=9.3s cache=245109/247686 (99%)
2026-06-19 18:49:23,254 INFO [20260619_153431_51fd01] agent.tool_executor: tool patch completed (0.10s, 1394 chars)
2026-06-19 18:49:23,287 INFO run_agent: OpenAI client created (chat_completion_stream_request, shared=False) thread=Thread-762 (_call):6421311488 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:49:26,661 INFO run_agent: OpenAI client closed (stream_request_complete, shared=False, tcp_force_closed=0) thread=Thread-762 (_call):6421311488 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:49:26,662 INFO [20260619_153431_51fd01] agent.conversation_loop: API call #143: model=anthropic/claude-opus-4.8 provider=openrouter in=248814 out=104 total=248918 latency=3.4s cache=246934/248814 (99%)
2026-06-19 18:49:27,958 INFO [20260619_153431_51fd01] agent.tool_executor: tool terminal completed (1.29s, 14487 chars)
2026-06-19 18:49:27,984 INFO run_agent: OpenAI client created (chat_completion_stream_request, shared=False) thread=Thread-764 (_call):6421311488 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:49:43,991 INFO run_agent: OpenAI client closed (stream_request_complete, shared=False, tcp_force_closed=0) thread=Thread-764 (_call):6421311488 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:49:43,992 INFO [20260619_153431_51fd01] agent.conversation_loop: API call #144: model=anthropic/claude-opus-4.8 provider=openrouter in=255375 out=938 total=256313 latency=16.0s cache=247771/255375 (97%)
2026-06-19 18:49:44,087 INFO [20260619_153431_51fd01] agent.conversation_loop: Turn ended: reason=text_response(finish_reason=stop) model=anthropic/claude-opus-4.8 api_calls=36/90 budget=31/90 tool_turns=129 last_msg_role=assistant response_len=2300 session=20260619_153431_51fd01
2026-06-19 18:49:44,112 INFO run_agent: OpenAI client created (agent_init, shared=True) thread=bg-review:6421311488 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:49:44,454 INFO [20260619_153431_51fd01] agent.turn_context: conversation turn: session=20260619_153431_51fd01 model=anthropic/claude-opus-4.8 provider=openrouter platform=cli history=310 msg='Review the conversation above and update the skill library. Be ACTIVE — most ses...'
2026-06-19 18:49:44,573 INFO run_agent: OpenAI client created (chat_completion_stream_request, shared=False) thread=Thread-765 (_call):6150942720 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:49:54,258 INFO run_agent: OpenAI client closed (stream_request_complete, shared=False, tcp_force_closed=0) thread=Thread-765 (_call):6150942720 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:49:54,259 INFO [20260619_153431_51fd01] agent.conversation_loop: API call #1: model=anthropic/claude-opus-4.8 provider=openrouter in=258322 out=423 total=258745 latency=9.8s cache=248822/258322 (96%)
2026-06-19 18:49:54,360 INFO [20260619_153431_51fd01] agent.tool_executor: tool skills_list completed (0.10s, 21152 chars)
2026-06-19 18:49:54,383 INFO run_agent: OpenAI client created (chat_completion_stream_request, shared=False) thread=Thread-766 (_call):6150942720 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:50:02,705 INFO run_agent: OpenAI client closed (stream_request_complete, shared=False, tcp_force_closed=0) thread=Thread-766 (_call):6150942720 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:50:02,706 INFO [20260619_153431_51fd01] agent.conversation_loop: API call #2: model=anthropic/claude-opus-4.8 provider=openrouter in=266258 out=313 total=266571 latency=8.3s cache=258320/266258 (97%)
2026-06-19 18:50:02,814 INFO [20260619_153431_51fd01] agent.tool_executor: tool skill_view completed (0.11s, 111769 chars)
2026-06-19 18:50:02,836 INFO [20260619_153431_51fd01] tools.tool_result_storage: Persisted large tool result: skill_view (toolu_01G7Zvw8ttjsUkomENppFu5T, 111769 chars -> /var/folders/p5/nqn3gs293rv3wtvf01pl9_vr0000gn/T/hermes-results/toolu_01G7Zvw8ttjsUkomENppFu5T.txt)
2026-06-19 18:50:02,861 INFO run_agent: OpenAI client created (chat_completion_stream_request, shared=False) thread=Thread-769 (_call):6150942720 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:50:12,687 INFO run_agent: OpenAI client closed (stream_request_complete, shared=False, tcp_force_closed=0) thread=Thread-769 (_call):6150942720 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:50:12,688 INFO [20260619_153431_51fd01] agent.conversation_loop: API call #3: model=anthropic/claude-opus-4.8 provider=openrouter in=267367 out=386 total=267753 latency=9.8s cache=258694/267367 (97%)
2026-06-19 18:50:12,749 INFO [20260619_153431_51fd01] agent.tool_executor: tool skill_view completed (0.06s, 22856 chars)
2026-06-19 18:50:12,776 INFO run_agent: OpenAI client created (chat_completion_stream_request, shared=False) thread=Thread-770 (_call):6150942720 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:50:18,989 INFO [20260619_153431_51fd01] agent.turn_context: conversation turn: session=20260619_153431_51fd01 model=anthropic/claude-opus-4.8 provider=openrouter platform=cli history=310 msg='yes'
2026-06-19 18:50:19,032 INFO run_agent: OpenAI client created (chat_completion_stream_request, shared=False) thread=Thread-772 (_call):12901707776 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:50:21,530 INFO run_agent: OpenAI client closed (stream_request_complete, shared=False, tcp_force_closed=0) thread=Thread-770 (_call):6150942720 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:50:21,531 INFO [20260619_153431_51fd01] agent.conversation_loop: API call #4: model=anthropic/claude-opus-4.8 provider=openrouter in=276591 out=415 total=277006 latency=8.8s cache=266515/276591 (96%)
2026-06-19 18:50:21,585 WARNING [20260619_153431_51fd01] agent.tool_executor: Tool skill_view returned error (0.05s): {"success": false, "error": "File 'references/stacked-feature-prs.md' not found in skill 'incremental-architecture-refactor'.", "available_files": {}, "hint": "Use one of the available file paths list
2026-06-19 18:50:21,613 INFO run_agent: OpenAI client created (chat_completion_stream_request, shared=False) thread=Thread-773 (_call):6150942720 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:50:36,019 INFO run_agent: OpenAI client closed (stream_request_complete, shared=False, tcp_force_closed=0) thread=Thread-772 (_call):12901707776 provider=openrouter base_url=https://openrouter.ai/api/v1 model=anthropic/claude-opus-4.8
2026-06-19 18:50:36,020 INFO [20260619_153431_51fd01] agent.conversation_loop: API call #145: model=anthropic/claude-opus-4.8 provider=openrouter in=256317 out=997 total=257314 latency=17.0s cache=256311/256317 (100%)
    3. not in debug.py _capture_default_log_snapshots() -> NEVER uploaded by
       ⚠️  This will upload the following to a public paste service:
  • System info (OS, Python version, Hermes version, provider, which API keys
    are configured — NOT the actual keys)
  • Recent log lines (agent.log, errors.log, gateway.log, desktop.log — may
    contain conversation fragments and file paths)
  • Full agent.log, gateway.log, and desktop.log (up to 512 KB each — likely
    contains conversation content, tool outputs, and file paths)

Pastes auto-delete after 6 hours.

Collecting debug report...
Uploading...

Debug report uploaded:
  Report  https://paste.rs/nnfZj

  (failed to upload: agent.log: Failed to upload to any paste service:
  paste.rs: HTTP Error 500: Internal Server Error
  dpaste.com: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:1016)>, gateway.log: Failed to upload to any paste service:
  paste.rs: HTTP Error 500: Internal Server Error
  dpaste.com: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:1016)>, desktop.log: Failed to upload to any paste service:
  paste.rs: HTTP Error 500: Internal Server Error
  dpaste.com: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:1016)>)

⏱  Pastes will auto-delete in 6 hours.
To delete now:  hermes debug delete <url>

Share these links with the Hermes team for support.
    4. still redacted via RedactingFormatter as defence-in-depth
- Disabled state attaches a NullHandler and sets the level above CRITICAL, so
  _capture_body() is a cheap no-op (single isEnabledFor check) on the hot path.
  Captured bodies are truncated to 4096 bytes. request.body() is Starlette-
  cached, so reading it in the access middleware does not consume the stream
  for downstream handlers.

Capture sites: HTTP request body (access middleware), PTY in/out frames.

Tests (tests/test_hermes_logging.py::TestBodyCaptureOptIn): disabled-by-default
creates no file and captures nothing; enabled writes to gui_bodies.log and the
payload is ABSENT from gui.log; large bodies truncate; the body logger is
isolated from every component; and the body file is excluded from both
LOG_FILES and the debug-share snapshot set.
exiao referenced this pull request in exiao/hermes-agent Jun 29, 2026
* fix(kanban): merge-ready PR completes to done, not blocked, for every lane

KANBAN_GUIDANCE step 5 told all coding lanes to kanban_block(review-required:)
for 'most coding tasks'. The merge-ready=done carve-out lived only in the kanban
skill docs, scoped to pr-babysitter, so build lanes (cpe-dev, PR #141) blocked a
green+approved+clean PR awaiting an Eric merge click. Rewrote the exception to
split real-judgment-owed (block) from merge-ready (complete), explicitly for
every lane that opens a PR.

Patch note: ~/.hermes/plans/hermes-patches/kanban-merge-ready-done.md

* fix(kanban): correct over-escaped quotes/newline and trim guidance under size budget

The expanded review-required exception used doubled escapes (\\" and \\n)
so the rendered prompt contained literal backslashes and a literal \n,
folding step 6 into the exception paragraph. Use single escapes so quotes
and the newline render correctly. Tighten wording so KANBAN_GUIDANCE fits
under the 5500-char ceiling enforced by test_kanban_guidance_prompt_size_bounded.
exiao referenced this pull request in exiao/hermes-agent Jun 29, 2026
…w lease push to feature branch) (#66)

* fix(approval): narrow force-push gate to match shell guard (auto-allow lease push to feature branch)

The runtime approval gate flagged EVERY force push for an operator yes/no,
regardless of target branch, so a routine force-with-lease PR rebase sat in
pending_approval and never ran headless — while the shell guard
(~/.local/bin/git) already allows exactly that case. Workers can't self-grant,
so they block and a human approves a push the shell layer considers safe
(observed: t_c5e6855d, t_d35127e9 on PR NousResearch#806's wt/t_89fe8606; PR #141 babysit).

Add a branch-aware carve-out to detect_dangerous_command: new helper
_is_safe_lease_push_to_feature_branch auto-allows a git push that carries
--force-with-lease, has no bare --force/-f, and does NOT target the default
branch. The main/master refspec forms it rejects (+main, HEAD:main, *:main,
bare main/master) mirror the shell guard's enumeration so the two layers can't
disagree. When it matches, the loop skips ONLY the three force-push pattern
descriptions; the main/master push backstop and every other pattern still fire.
The lease is the safety belt (refuses if the remote moved), so a shared-worktree
second pusher bounces instead of clobbering; a bare force has no lease and keeps
prompting.

Verified (real invocations): (a) lease->feature branch auto-approves headless
(no prompt) via check_all_command_guards in a gateway context; (b) bare force
still prompts; (c) lease->main still prompts/blocks; (d) a stale lease real git
push is rejected (stale info), remote not clobbered. Differential probe shows
the carve-out un-gates ONLY the lease-to-feature shapes, all other verdicts
unchanged. Updated test_git_push_force_with_lease_* + added two regression
tests; 262 approval tests pass, ruff clean.

Patch note: ~/.hermes/plans/hermes-patches/force-push-lease-feature-branch-carveout.md

* fix(approval): gate refs/heads/main force-push forms + harden lease carve-out

Reviewer (kanban t_8a8f70fe) FAILED #66: two main-rewriting shapes
auto-approved headless because _DEFAULT_BRANCH_PUSH_RE and the
DANGEROUS_PATTERNS main/master entries missed the fully-qualified ref forms.

- _DEFAULT_BRANCH_PUSH_RE: anchor main/master on a refspec-boundary char
  (^|[/:+\s]) ... (\s|$|['"`]) so refs/heads/main, HEAD:refs/heads/main,
  refs/heads/topic:refs/heads/main, *:main and the quoted '*:main' are all
  gated, while names that merely contain the word (mainline/my-main/main-event)
  are not.
- DANGEROUS_PATTERNS push-to-main backstop: same boundary anchoring so the
  qualified forms are caught even when the carve-out is not in play.
- Harden _is_safe_lease_push_to_feature_branch (codex P1s, same bug class):
  reject --all/--mirror broadcasts, a push with NO explicit destination
  refspec (push.default may push current branch=main), and a leading-`+`
  refspec (forced update with no lease guarantee). Carve-out now auto-approves
  ONLY an explicit, single, non-`+`, non-default feature ref.
- Tests: extend the main/master loops with the refs/heads forms (main+master),
  *:main quoted+unquoted, refs/heads RHS; add qualified-feature negatives and a
  new test for the +refspec / broadcast / no-refspec classes.

Suite: 262 passed / 2 failed (pre-existing prompt_toolkit ModuleNotFoundError,
unrelated).

* fix(approval): exclude trailing-hyphen flags from lease-push detection

`_FORCE_WITH_LEASE_RE` used `(?![\\w])` which let a different flag that
merely starts with the lease string (e.g. `--force-with-lease-foo`) read as
the lease form. Switch to `(?![\\w-])` so a trailing hyphen also fails the
boundary, matching the convention the bare-force regexes already use; the
`=<expected>` value form is unaffected. Adds a regex-boundary test.

Addresses gemini-code-assist review (line 869).

* fix(approval): harden lease-push carve-out (codex P1/P2 follow-up to #66)

Follow-up to #66. Codex left genuine hardening gaps in
_is_safe_lease_push_to_feature_branch — every case below was reproduced
auto-approving headless on the pr-66 head before this change:

- live-config exclusion (P1): add _PROTECTED_BRANCHES = {main, master,
  live-config}. live-config is the long-lived integration branch the running
  gateway checks out (AGENTS.md), so a headless leased force-with-lease push to
  it would bypass the PR-to-self flow. Matched against the parsed destination
  branch in bare / refs/heads / HEAD: / src:dst forms.
- branch-ref-only carve-out (P2 "Restrict to branch refs"): new
  _refspec_destination() parses each refspec's destination and returns None for
  anything that is not a single refs/heads/<branch>. refs/tags, refs/notes,
  refs/remotes and a tag RHS (feature:refs/tags/v1) now keep prompting — a
  leased tag rewrite no longer slips through.
- reject ambiguous shorthands (P1): HEAD (resolves to the current checkout to
  main when on main), the bare colon matching-refspec, and wildcards
  (refs/heads/*) are rejected by the destination parser.
- don't miscount flag values / shell prefix (P1): strip everything up to and
  including the push verb (so "cd repo && ..." no longer leaves phantom refspec
  tokens) and drop space-separated value-flag VALUES (-o ci.skip,
  --push-option ci.skip, --repo, --receive-pack, --exec) before the refspec
  count. Boolean flags (--force-if-includes, -q) are NOT in the value-flag set,
  so they don't eat the following positional.
- reject delete pushes (P1, same carve-out surface): --delete/-d removes the
  remote ref and the flag-strip would leave it reading like a routine rebase;
  now rejected up front.
- normalize quoted destinations: strip surrounding shell quotes so HEAD:'main'
  / 'master' / 'live-config' are still caught while 'feature' still carves out.

Stale Codex threads (refs/heads/main gating, leading-plus, force-with-lease-foo
boundary, *:main quoted) were already fixed on the pr-66 head and are resolved
separately.

Tests: 28 new assertions across 7 methods in tests/tools/test_approval.py
covering protected branches, non-branch refs, shorthands, flag-value/prefix
miscount, delete pushes, quoted destinations, and boolean-flag non-consumption,
plus ordinary-feature negatives (mainline/my-main/feature/head still carve out).
tests/tools/test_approval.py: 272 passed / 0 failed.

* fix(approval): close 3 codex P1 gaps in lease-push carve-out (#66)

Second Codex pass on the pr-66 head flagged 3 more headless auto-approve
gaps in _is_safe_lease_push_to_feature_branch — each reproduced before
this change:

- reject --tags pushes (P1): `--tags` pushes tags alongside the named
  refspec, so a leased `--force-with-lease=refs/tags/v1:<old> --tags
  origin feature` can force-update a tag while the visible refspec looks
  like a routine feature push. Added `tags` to the broadcast rejection
  (next to --all/--mirror). (--tags has no short form on git push.)
- consume --recurse-submodules value (P1): the space form
  `--recurse-submodules on-demand` consumes the next word; without
  dropping the value it survived as `origin` and got miscounted as a
  refspec, carving out an omitted-refspec push (push.default → could be
  main). Added to _VALUE_FLAGS.
- strip numeric short flags (P1): the generic flag-strip only matched
  letter-first flags, so `-4`/`-6` (IPv4/IPv6) survived and `origin` was
  misread as a refspec. Broadened the leading char class to `[a-z0-9]`
  (a remote/refspec never starts with a dash, so flags only).

Tests: 3 new methods in tests/tools/test_approval.py (block on the bare
forms, still carve out with a real feature refspec). 276 passed / 0
failed.

* fix(approval): reject colon-prefix branch delete in lease carve-out (PR #66 P1)

A leased colon-prefix delete push (the `:branch` shorthand that removes a
remote branch) returned carve=True and auto-approved the deletion headlessly.
The --delete/-d flag forms were already rejected, but _refspec_destination
only rejected an empty DST (src: = delete), never an empty SOURCE (:dst). The
split-on-colon returned the RHS as a clean destination, so no DANGEROUS_PATTERN
caught it; protected :main/:live-config were saved only incidentally.

Fix: in _refspec_destination, reject (return None) when the SOURCE side of
src:dst is empty -- a :dst push is a branch delete and must NOT auto-approve.
Added the colon-prefix delete cases to
test_git_push_lease_carveout_rejects_delete_pushes.

Patch note: ~/.hermes/plans/hermes-patches/force-push-lease-feature-branch-carveout.md

* fix(approval): reject packed numeric+force short bundle in lease carve-out (#66 P1)

Codex flagged a packed short-flag bypass beyond the prior `-4` flag-strip
fix: `git push -h` lists numeric `-4`/`-6` (IPv4/IPv6) AND `-f` force, and
Git accepts packed combos like `-4f` (IPv4 + bare force). The bare-force
detector `_SHORT_FORCE_FLAG_RE` only allowed LETTERS around `f`
(`-[a-z]*f[a-z]*`), so `-4f` was not recognized as a bare force; the
generic flag-strip then erased the whole token and
`_is_safe_lease_push_to_feature_branch()` carved out a push that overrides
the lease with no safety belt.

Fix: broaden the short-force char class to `[a-z0-9]`
(`-[a-z0-9]*f[a-z0-9]*`) so `-4f`/`-6f`/`-f4` are caught as a bare force and
keep prompting. A packed bundle without `f` (e.g. `-uq`) is unaffected.

Tests: test_git_push_lease_carveout_rejects_packed_numeric_force_bundle
(blocks -4f/-6f/-f4, still carves out -uq). 277 passed / 0 failed.

* fix(approval): reject `git push <remote> tag <name>` shorthand in lease carve-out (#66 P1)

Codex flagged the tag-shorthand bypass: git documents `git push <remote>
tag <tag>` as sugar for `refs/tags/<tag>:refs/tags/<tag>`, so a leased
`--force-with-lease=refs/tags/v1:<old> origin tag v1` force-updates a TAG
while the parsed tokens (`tag`, `v1`) both look like ordinary branch
refspecs and the branch-only carve-out wrongly skipped the force-push
approval.

Fix: reject when the first refspec token is the literal `tag` AND a name
trails it (the shorthand requires both). A lone branch literally named
`tag` (`git push origin tag`) is unaffected — it's validated as a normal
branch destination.

Tests: test_git_push_lease_carveout_rejects_tag_shorthand (blocks the
`tag v1` shorthand + the explicit-lease tag form, still carves out a lone
`tag` branch). 278 passed / 0 failed.

* fix(approval): close 3 more lease carve-out gaps; shlex-tokenize args (#66 P1)

Codex 6th-pass follow-up. Three cleanly-fixable bypasses on the pr-66 head:

- packed numeric DELETE bundle (`-4d` = IPv4 + delete): the delete guard
  only allowed letters around `d`, so `-4d` slipped past and the flag-strip
  erased it, deleting a remote branch headlessly. Broadened to `[a-z0-9]`
  (mirrors the `-4f` packed-force fix).
- `tag <name>` shorthand at ANY refspec position: git accepts `origin
  feature tag v1`, not just `origin tag v1`. The check only rejected
  `refspecs[0] == 'tag'`; now rejects any `tag` token followed by a name
  (a lone trailing `tag` is still an ordinary branch named "tag").
- quoted option value with whitespace (`--push-option 'ci skip'`): the
  plain whitespace split leaked `skip` as a phantom refspec. Replaced the
  regex flag-strip + `str.split()` with `shlex.split()` (try/except → keep
  prompting on unbalanced quotes) and token-level value-flag consumption, so
  a quoted value is one argument and is consumed whole.

Tests: 2 new methods (packed numeric delete, quoted option value) +
extended the tag-shorthand test to cover the trailing-position form. 280
passed / 0 failed.

* fix(approval): reject unqualified colon push destinations

* fix(approval): drop bare `--` option terminator before refspec count (#66 P1)

Codex 7th-pass: `git push --force-with-lease origin --` survived the
flag-strip (the bare `--` matches no `[a-z0-9]` flag pattern), so tokens
became `[origin, --]`, len >= 2 passed the explicit-refspec check, and the
carve-out skipped the force-push approval — but git treats `--` as the
option terminator, NOT a refspec, so it's an omitted-refspec push that
follows push.default (could rewrite the current protected branch).

Fix: drop the bare `--` token alongside the flag-strip so it doesn't count
toward the explicit-destination check. A real refspec after `--`
(`origin -- feature`) still carves out.

Tests: test_git_push_lease_carveout_rejects_bare_option_terminator. 282
passed / 0 failed.

* fix(approval): scope lease-push backstop to refspecs

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

This PR claims to add comprehensive tests but instead deletes significant portions of the existing test suite, including TestListSessions, TestActiveQueries, TestPruning, TestCheckpoint, TestKillProcess, and TestProcessToolHandler. This reduces coverage drastically. I will push a fix to restore the deleted coverage while keeping the valid new tests (test_output_buffer_truncation and TestConstants).

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

Review findings: The PR implements the ACTUAL_MEMORIES provider correctly but lacks automated tests. I have pushed a fix to add the missing tests for lancedb_agent.

Meraniya pushed a commit to Meraniya/hermes-agent that referenced this pull request Aug 6, 2026
CI's supply-chain-audit policy requires <next_major ceilings on all
PyPI deps (CONTRIBUTING.md § dependency pinning policy). NousResearch#140 bumped
the floor (61.0 -> 83.0.0) without adding one; the follow-up fix
merged after the auto-merge had already fired on the earlier commit,
so main was left with the unbounded spec.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
29206394 pushed a commit to 29206394/hermes-agent that referenced this pull request Aug 10, 2026
…usResearch#141)

Root cause: /api/profiles/sessions (+ sidebar) opened a fresh read-only
SQLite connection per profile per request; in WAL mode each connection
holds 2 fds (state.db + -wal). Slow queries on large profile DBs plus
client disconnects that leave server-side workers mid-query piled up
open fds past the process limit -> Errno 24 -> socket.accept() out of
system resource -> dashboard unreachable (Desktop 'SSL_CERT_FILE cannot
be loaded' was the first failed open).

Fix: hermes_cli/web_routers/_session_db_pool.py gives each profile ONE
reusable read-only SessionDB (checked out per request, TTL 30s) plus at
most 2 short-lived borrows; requests beyond that block instead of
opening more connections. Wired into get_profiles_sessions,
get_profiles_sessions_sidebar and _open_session_db_for_profile's
read-only path (covers /api/sessions, /search, /detail, ...). Fresh
handles are schema-probed; poisoned handles are invalidated.

Verified: 6 new unit tests (tests/test_session_db_pool.py) green; HTTP
abort-load test: dev/state.db fds stayed <= 8 (was ~54 and hit Errno 24
before the fix); per-profile steady state is exactly 1 connection.
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.

3 participants