fix(delegation): forward background flag so delegate_task(background=true) runs async#46968
Merged
Conversation
Port from Kilo-Org/kilocode#11240. Their issue #11227 lost a user's entire working directory: a built-in-skill sentinel location resolved to the server cwd and the skill-removal endpoint ran a recursive delete on it. Hermes' /skills uninstall path (skills_hub.py) is already hardened, but the agent-facing skill_manage(action='delete') path did a bare shutil.rmtree(skill_dir) with no last-line validation. Add _validate_delete_target(): refuse to rmtree a path that (1) isn't strictly inside a known skills root, (2) is a skills root itself, or (3) is reached via a symlink/junction. Tests: 4 cases (normal delete works; symlinked dir, skills-root, out-of-tree all refused). E2E verified with real symlink + file I/O.
delegate_task is an _AGENT_LOOP_TOOLS member, so every surface (CLI, gateway, desktop/TUI) routes it through AIAgent._dispatch_delegate_task. That forwarder passed every schema field except background, so delegate_task(background=true) was silently downgraded to a synchronous run and returned the sync results payload instead of a delegation_id. The model sees background in the schema (the call validates), but the value never reached the function. Add the one missing kwarg so async background delegation actually engages.
Contributor
🔎 Lint report:
|
| Rule | Count |
|---|---|
unresolved-attribute |
2 |
First entries
tests/run_agent/test_credits_notices_toggle.py:76: [unresolved-attribute] unresolved-attribute: Unresolved attribute `_credits_session_start_micros` on type `AIAgent`
run_agent.py:2920: [unresolved-attribute] unresolved-attribute: Object of type `Self@get_credits_spent_micros` has no attribute `_credits_session_start_micros`
✅ Fixed issues (1):
| Rule | Count |
|---|---|
invalid-assignment |
1 |
First entries
tests/run_agent/test_credits_notices_toggle.py:76: [invalid-assignment] invalid-assignment: Object of type `None` is not assignable to attribute `_credits_session_start_micros` of type `int`
Unchanged: 5771 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
This was referenced Jun 16, 2026
jhjaggars-hermes
added a commit
to jhjaggars/hermes-agent
that referenced
this pull request
Jun 16, 2026
* fix(skills): guard recursive skill delete against tree-escape (NousResearch#46929) Port from Kilo-Org/kilocode#11240. Their issue NousResearch#11227 lost a user's entire working directory: a built-in-skill sentinel location resolved to the server cwd and the skill-removal endpoint ran a recursive delete on it. Hermes' /skills uninstall path (skills_hub.py) is already hardened, but the agent-facing skill_manage(action='delete') path did a bare shutil.rmtree(skill_dir) with no last-line validation. Add _validate_delete_target(): refuse to rmtree a path that (1) isn't strictly inside a known skills root, (2) is a skills root itself, or (3) is reached via a symlink/junction. Tests: 4 cases (normal delete works; symlinked dir, skills-root, out-of-tree all refused). E2E verified with real symlink + file I/O. * fix(delegation): forward background flag so delegate_task(background=true) runs async (NousResearch#46968) * fix(skills): guard recursive skill delete against tree-escape Port from Kilo-Org/kilocode#11240. Their issue NousResearch#11227 lost a user's entire working directory: a built-in-skill sentinel location resolved to the server cwd and the skill-removal endpoint ran a recursive delete on it. Hermes' /skills uninstall path (skills_hub.py) is already hardened, but the agent-facing skill_manage(action='delete') path did a bare shutil.rmtree(skill_dir) with no last-line validation. Add _validate_delete_target(): refuse to rmtree a path that (1) isn't strictly inside a known skills root, (2) is a skills root itself, or (3) is reached via a symlink/junction. Tests: 4 cases (normal delete works; symlinked dir, skills-root, out-of-tree all refused). E2E verified with real symlink + file I/O. * fix(delegation): forward background flag in delegate_task dispatch delegate_task is an _AGENT_LOOP_TOOLS member, so every surface (CLI, gateway, desktop/TUI) routes it through AIAgent._dispatch_delegate_task. That forwarder passed every schema field except background, so delegate_task(background=true) was silently downgraded to a synchronous run and returned the sync results payload instead of a delegation_id. The model sees background in the schema (the call validates), but the value never reached the function. Add the one missing kwarg so async background delegation actually engages. * feat(desktop): open new sessions in compact windows Add the Electron IPC bridge and rebindable shortcut for opening an unkeyed scratch window on the new-session draft. * feat(desktop): trim scratch window chrome Hide nonessential Hermes chrome in the new-session pop-out while preserving native window controls and stable first-message positioning. * fix(desktop): sync new sessions across windows Broadcast session-list mutations from scratch windows so the main sidebar refreshes without manual reloads. * Hide hosted dashboard update controls * Detect containerized dashboard update management * Simplify dashboard update detection to containers * fix(desktop): route global remote profile REST calls (NousResearch#47011) * fix(desktop): route global remote profile REST calls * fix(dashboard): scope oauth provider routes by profile * test(tui): isolate notification poller queue * fix(desktop): open remote-gateway artifacts via authenticated download (NousResearch#46895) On a remote gateway connection, agent-written files live on the gateway host, not the desktop's disk, so the Artifacts view's file:// hrefs failed ("Invalid external URL") and image thumbnails broke. Make mediaExternalUrl() remote-aware in one place: in remote mode it rewrites gateway-local paths to GET /api/files/download (a new endpoint that streams the file as a Content-Disposition: attachment). The artifacts view now resolves through it, and so do the existing chat-media and generated-image callers, for free. The download endpoint stays auth-gated; auth_middleware additionally accepts the session token as a ?token= query param for this one path so a shell/browser-opened download (which can't set the session header) still authenticates — the same query-token tradeoff as the /api/pty WebSocket. It is NOT added to PUBLIC_API_PATHS. Salvages NousResearch#46663 (which carried ~19k lines of CRLF noise and made the endpoint public). Reimplemented on a clean LF base with the security hole closed and tests added. Co-authored-by: qingshan89 <qs2816661685@gmail.com> * Sync homelab/main to upstream/main (9d2ec8d) with minimal carried patches Rebased onto current upstream/main and reapplied the minimal homelab patch set: - Dockerfile: add iproute2 + GitHub CLI (gh) from official apt repo - pyproject.toml: add langfuse optional extra - plugins/observability/langfuse/__init__.py: Responses API serialization - plugins/platforms/discord/adapter.py: role-mention invocation support - tests/gateway/test_discord_role_mentions.py: role-mention test coverage - tests/plugins/test_langfuse_plugin.py: Responses API test coverage - .github/workflows/build.yml: GHCR image publish workflow --------- Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com> Co-authored-by: Brooklyn Nicholson <brooklyn.bb.nicholson@gmail.com> Co-authored-by: Shannon Sands <shannon.sands.1979@gmail.com> Co-authored-by: Gille <4317663+helix4u@users.noreply.github.com> Co-authored-by: qingshan89 <qs2816661685@gmail.com> Co-authored-by: Hermes Agent <hermes-agent@users.noreply.github.com>
pib-hermes-bot
pushed a commit
to Partners-in-Biz/hermes-agent
that referenced
this pull request
Jun 16, 2026
…true) runs async (NousResearch#46968) * fix(skills): guard recursive skill delete against tree-escape Port from Kilo-Org/kilocode#11240. Their issue NousResearch#11227 lost a user's entire working directory: a built-in-skill sentinel location resolved to the server cwd and the skill-removal endpoint ran a recursive delete on it. Hermes' /skills uninstall path (skills_hub.py) is already hardened, but the agent-facing skill_manage(action='delete') path did a bare shutil.rmtree(skill_dir) with no last-line validation. Add _validate_delete_target(): refuse to rmtree a path that (1) isn't strictly inside a known skills root, (2) is a skills root itself, or (3) is reached via a symlink/junction. Tests: 4 cases (normal delete works; symlinked dir, skills-root, out-of-tree all refused). E2E verified with real symlink + file I/O. * fix(delegation): forward background flag in delegate_task dispatch delegate_task is an _AGENT_LOOP_TOOLS member, so every surface (CLI, gateway, desktop/TUI) routes it through AIAgent._dispatch_delegate_task. That forwarder passed every schema field except background, so delegate_task(background=true) was silently downgraded to a synchronous run and returned the sync results payload instead of a delegation_id. The model sees background in the schema (the call validates), but the value never reached the function. Add the one missing kwarg so async background delegation actually engages.
alanbratu
pushed a commit
to alanbratu/hermes-agent
that referenced
this pull request
Jun 17, 2026
…true) runs async (NousResearch#46968) * fix(skills): guard recursive skill delete against tree-escape Port from Kilo-Org/kilocode#11240. Their issue NousResearch#11227 lost a user's entire working directory: a built-in-skill sentinel location resolved to the server cwd and the skill-removal endpoint ran a recursive delete on it. Hermes' /skills uninstall path (skills_hub.py) is already hardened, but the agent-facing skill_manage(action='delete') path did a bare shutil.rmtree(skill_dir) with no last-line validation. Add _validate_delete_target(): refuse to rmtree a path that (1) isn't strictly inside a known skills root, (2) is a skills root itself, or (3) is reached via a symlink/junction. Tests: 4 cases (normal delete works; symlinked dir, skills-root, out-of-tree all refused). E2E verified with real symlink + file I/O. * fix(delegation): forward background flag in delegate_task dispatch delegate_task is an _AGENT_LOOP_TOOLS member, so every surface (CLI, gateway, desktop/TUI) routes it through AIAgent._dispatch_delegate_task. That forwarder passed every schema field except background, so delegate_task(background=true) was silently downgraded to a synchronous run and returned the sync results payload instead of a delegation_id. The model sees background in the schema (the call validates), but the value never reached the function. Add the one missing kwarg so async background delegation actually engages.
zmlgit
pushed a commit
to zmlgit/hermes-agent
that referenced
this pull request
Jun 17, 2026
…true) runs async (NousResearch#46968) * fix(skills): guard recursive skill delete against tree-escape Port from Kilo-Org/kilocode#11240. Their issue NousResearch#11227 lost a user's entire working directory: a built-in-skill sentinel location resolved to the server cwd and the skill-removal endpoint ran a recursive delete on it. Hermes' /skills uninstall path (skills_hub.py) is already hardened, but the agent-facing skill_manage(action='delete') path did a bare shutil.rmtree(skill_dir) with no last-line validation. Add _validate_delete_target(): refuse to rmtree a path that (1) isn't strictly inside a known skills root, (2) is a skills root itself, or (3) is reached via a symlink/junction. Tests: 4 cases (normal delete works; symlinked dir, skills-root, out-of-tree all refused). E2E verified with real symlink + file I/O. * fix(delegation): forward background flag in delegate_task dispatch delegate_task is an _AGENT_LOOP_TOOLS member, so every surface (CLI, gateway, desktop/TUI) routes it through AIAgent._dispatch_delegate_task. That forwarder passed every schema field except background, so delegate_task(background=true) was silently downgraded to a synchronous run and returned the sync results payload instead of a delegation_id. The model sees background in the schema (the call validates), but the value never reached the function. Add the one missing kwarg so async background delegation actually engages.
al3xar
pushed a commit
to al3xar/hermes-agent
that referenced
this pull request
Jun 17, 2026
…true) runs async (NousResearch#46968) * fix(skills): guard recursive skill delete against tree-escape Port from Kilo-Org/kilocode#11240. Their issue NousResearch#11227 lost a user's entire working directory: a built-in-skill sentinel location resolved to the server cwd and the skill-removal endpoint ran a recursive delete on it. Hermes' /skills uninstall path (skills_hub.py) is already hardened, but the agent-facing skill_manage(action='delete') path did a bare shutil.rmtree(skill_dir) with no last-line validation. Add _validate_delete_target(): refuse to rmtree a path that (1) isn't strictly inside a known skills root, (2) is a skills root itself, or (3) is reached via a symlink/junction. Tests: 4 cases (normal delete works; symlinked dir, skills-root, out-of-tree all refused). E2E verified with real symlink + file I/O. * fix(delegation): forward background flag in delegate_task dispatch delegate_task is an _AGENT_LOOP_TOOLS member, so every surface (CLI, gateway, desktop/TUI) routes it through AIAgent._dispatch_delegate_task. That forwarder passed every schema field except background, so delegate_task(background=true) was silently downgraded to a synchronous run and returned the sync results payload instead of a delegation_id. The model sees background in the schema (the call validates), but the value never reached the function. Add the one missing kwarg so async background delegation actually engages.
T02200059
pushed a commit
to T02200059/hermes-agent
that referenced
this pull request
Jun 18, 2026
…true) runs async (NousResearch#46968) * fix(skills): guard recursive skill delete against tree-escape Port from Kilo-Org/kilocode#11240. Their issue NousResearch#11227 lost a user's entire working directory: a built-in-skill sentinel location resolved to the server cwd and the skill-removal endpoint ran a recursive delete on it. Hermes' /skills uninstall path (skills_hub.py) is already hardened, but the agent-facing skill_manage(action='delete') path did a bare shutil.rmtree(skill_dir) with no last-line validation. Add _validate_delete_target(): refuse to rmtree a path that (1) isn't strictly inside a known skills root, (2) is a skills root itself, or (3) is reached via a symlink/junction. Tests: 4 cases (normal delete works; symlinked dir, skills-root, out-of-tree all refused). E2E verified with real symlink + file I/O. * fix(delegation): forward background flag in delegate_task dispatch delegate_task is an _AGENT_LOOP_TOOLS member, so every surface (CLI, gateway, desktop/TUI) routes it through AIAgent._dispatch_delegate_task. That forwarder passed every schema field except background, so delegate_task(background=true) was silently downgraded to a synchronous run and returned the sync results payload instead of a delegation_id. The model sees background in the schema (the call validates), but the value never reached the function. Add the one missing kwarg so async background delegation actually engages.
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
delegate_task(background=true)now actually runs the subagent asynchronously instead of silently blocking. Previously thebackgroundflag was dropped before reaching the function, so it always ran synchronously and returned the sync results payload instead of adelegation_id.Root cause:
delegate_taskis an_AGENT_LOOP_TOOLSmember, so every surface (CLI, gateway, desktop/TUI) routes it through the single chokepointAIAgent._dispatch_delegate_task(run_agent.py). That forwarder passed every schema field exceptbackground. The model seesbackgroundin the schema (the call validates), but the value never reacheddelegate_task, which defaulted it toFalseand ran synchronously. The generic registry handler does forward it, but that path is only a fallback stub for agent-loop tools and is never the live path.Not desktop-specific despite the report framing — it affected every surface.
Changes
run_agent.py: addbackground=function_args.get("background")to_dispatch_delegate_task.Validation
backgroundreachesdelegate_tasktests/tools/test_async_delegation.pyE2E: stubbed
delegate_taskand invoked the real_dispatch_delegate_task({"goal":..., "background": True})— confirmedbackground=Truenow arrives at the function (wasNone/dropped before).Infographic