Hermes Web UI Version
0.6.35
Hermes Agent Version
0.19.0
Bug Description
When approving skill write requests in the Write Gate panel, the UI shows a green "Approved" success toast, but the skill file is not actually modified and the pending item remains in the list after refresh.
Root cause: the approve endpoint in packages/server/src/controllers/hermes/write-gate.ts always returns { success: true } regardless of whether the Python subprocess actually succeeded. The Python helper (handle_pending_subcommand) returns error messages as plain text strings without raising exceptions, so the controller never enters the catch block. The frontend (PendingWriteApprovals.vue) only checks HTTP status and never inspects the output field, so HTTP 200 always triggers a green success toast.
Additionally, pendingAction is a single ref shared across all approve/reject buttons. Clicking approve on multiple items in quick succession overwrites the loading state, allowing concurrent API requests. When multiple pending writes target the same skill, concurrent Python subprocesses race on the same file and fail silently.
Steps to Reproduce
- Have multiple pending skill writes in the Write Gate panel (5+ items, ideally some targeting the same skill)
- Click "Approve" buttons from bottom to top in quick succession (don't wait for each to finish)
- Observe green success toasts appearing for each click
- After all toasts disappear, observe the pending items are still in the list
- Check the target skill files — they were not modified
Note: clicking approve from top to bottom, one at a time waiting for each to complete, works correctly. The issue only manifests when clicking rapidly from bottom to top, which triggers concurrent requests due to the shared pendingAction ref.
Expected Behavior
- Each approved skill write should be applied to the skill file and the pending JSON record should be deleted
- If approval fails (e.g. patch conflict, file not found), the UI should show a red error toast with the failure reason, not a green success toast
- Each approve button should have independent loading/disabled state to prevent concurrent approvals
Actual Behavior
- Green "Approved" success toast appears for every click regardless of actual result
- Pending items remain in the list after refresh — the pending JSON files were never deleted
- Target skill files are not modified
- Multiple approve buttons can be clicked concurrently because pendingAction is a single string ref — clicking button D clears button E's loading state, re-enabling E
Logs / Error Messages
No error is surfaced to the user. The API returns HTTP 200 with:
{
"success": true,
"output": "Approved 0 skills write(s).\nFailed:\n <id>: No pending skills write with id '<id>'."
}
The output field contains the failure message, but neither the server controller nor the frontend checks it. The controller hardcodes success: true (write-gate.ts line 59), and the frontend only checks res.ok (PendingWriteApprovals.vue line 135-136).
After the incident, 21 orphaned pending JSON files were found in ~/.hermes/pending/skills/ (default profile path), while ~/.hermes/profiles/elysia/pending/skills/ (active profile) was empty. This suggests HERMES_HOME is not correctly propagated to the Agent process when launched by Studio with a non-default profile.
Environment
macOS
Node Version
v24.18.0
Additional Context
Relevant source locations:
- packages/server/src/controllers/hermes/write-gate.ts — approve() hardcodes success: true (line 59)
- packages/client/src/components/hermes/skills/PendingWriteApprovals.vue — resolvePendingWrite() never checks output field (line 134-136); pendingAction is a single ref (line 24)
- packages/server/src/services/hermes/write-gate.ts — runPythonAction() passes HERMES_HOME=profileDir to subprocess, but the Agent process that creates pending files may resolve HERMES_HOME differently
Suggested fixes:
- Parse Python output for failure indicators or have the Python helper exit non-zero on failure
- Frontend should check the output field and show error toast on failure
- Make pendingAction a Set for per-button independent loading state
- Add optimistic UI removal after successful API call
- Verify HERMES_HOME propagation for non-default profiles
Hermes Web UI Version
0.6.35
Hermes Agent Version
0.19.0
Bug Description
When approving skill write requests in the Write Gate panel, the UI shows a green "Approved" success toast, but the skill file is not actually modified and the pending item remains in the list after refresh.
Root cause: the approve endpoint in packages/server/src/controllers/hermes/write-gate.ts always returns { success: true } regardless of whether the Python subprocess actually succeeded. The Python helper (handle_pending_subcommand) returns error messages as plain text strings without raising exceptions, so the controller never enters the catch block. The frontend (PendingWriteApprovals.vue) only checks HTTP status and never inspects the output field, so HTTP 200 always triggers a green success toast.
Additionally, pendingAction is a single ref shared across all approve/reject buttons. Clicking approve on multiple items in quick succession overwrites the loading state, allowing concurrent API requests. When multiple pending writes target the same skill, concurrent Python subprocesses race on the same file and fail silently.
Steps to Reproduce
Note: clicking approve from top to bottom, one at a time waiting for each to complete, works correctly. The issue only manifests when clicking rapidly from bottom to top, which triggers concurrent requests due to the shared pendingAction ref.
Expected Behavior
Actual Behavior
Logs / Error Messages
No error is surfaced to the user. The API returns HTTP 200 with: { "success": true, "output": "Approved 0 skills write(s).\nFailed:\n <id>: No pending skills write with id '<id>'." } The output field contains the failure message, but neither the server controller nor the frontend checks it. The controller hardcodes success: true (write-gate.ts line 59), and the frontend only checks res.ok (PendingWriteApprovals.vue line 135-136). After the incident, 21 orphaned pending JSON files were found in ~/.hermes/pending/skills/ (default profile path), while ~/.hermes/profiles/elysia/pending/skills/ (active profile) was empty. This suggests HERMES_HOME is not correctly propagated to the Agent process when launched by Studio with a non-default profile.Environment
macOS
Node Version
v24.18.0
Additional Context
Relevant source locations:
Suggested fixes: