HERMES-GIELIE/api
b3nw 2ddef230ff fix: drop auto-delete; manual-instruction only (PR #5688 round-3)
Address round-2 gate cert (issuecomment-4896321817) on sha:dcec232d:

1. BRICK (Codex strace-verified): v2's fcntl-flock holder check on
   .git/index.lock was wrong. Codex straced git 2.43.0 and proved
   that git uses O_CREAT|O_EXCL + rename(2), NOT advisory locking,
   so an fcntl-flock probe returns False for a lock file a live git
   process is holding open. The 'fail-closed' claim was wrong:
   auto-delete could still race and corrupt the index.

2. CORE (Codex): if .git/index.lock vanished between _is_lock_held
   and os.remove, the FileNotFoundError surfaced as a 'remove-failed'
   diagnostic instead of 'already gone' and prevented the apply retry.

Fix: stop deleting lock files from the server entirely. The only thing
that removes a lock is the user, on the host, via the manual command
surfaced in the response. Once the lock is gone, the user re-clicks
Update Now and the normal non-destructive apply path runs.

Implementation:

api/updates.py:
  - Removed _is_lock_held (fcntl-flock probe) -- was provably unsafe.
  - Removed _try_remove_lock (os.remove path) -- was provably unsafe.
  - Removed _GIT_LOCK_FILES_REMOVABLE (no longer enumerable by name).
  - Added _inventory_locks(path) -> dict: pure inventory of well-known
    + other lock files under .git/. Never touches anything.
  - Rewrote apply_clear_lock to inventory-only + manual-instruction.
    When .git/index.lock is absent, runs the normal non-destructive
    apply path; when present, returns ok=False with the exact 'rm -f'
    command + an explanation of why the server cannot do this safely
    (O_CREAT|O_EXCL cannot be detected with advisory probes).
    CORE-1 race evaporates: there is no os.remove to race against.

api/routes.py:
  - Updated the /api/updates/clear_lock comment to reflect v2.2.

static/ui.js:
  - applyClearUpdateLock: when res.lock_held, call new
    _renderLockManualInstruction(target, res) which surfaces the exact
    'rm -f <path>' command in a copyable code block plus an
    'I've removed the lock -- retry update' button that POSTs the
    same endpoint again; the second call takes the success branch
    and re-runs the normal apply path. Also lists any other lock
    files present so the operator can investigate.

tests/test_updates.py:
  - Removed all 4 tests for the deleted v2 helpers (_is_lock_held
    Returns*, _try_remove_lock*).
  - Added test_v2_probe_helpers_removed: regression guard that
    fails loud if either helper is ever reintroduced.
  - Rewrote test_apply_clear_lock_removes_unheld_lock_and_runs_normal_update
    and test_apply_clear_lock_refuses_when_lock_held for the v2.2
    contract. The 'refuses' test monkeypatches os.remove to record
    any deletion attempt and asserts the call list is empty -- a
    strict runtime guard against future regressions to auto-delete.
  - Added 7 new tests: test_inventory_locks_* (4), test_apply_clear
    lock_with_no_lock_runs_normal_update, test_apply_clear_lock_with
    lock_present_returns_manual_instruction, test_apply_clear_lock
    listing_includes_other_locks, test_apply_clear_lock_rejects
    unknown_target, test_apply_clear_lock_rejects_not_git_repo.

Verification:
  ./scripts/test.sh tests/test_updates.py          -> 73 passed
  ./scripts/test.sh tests/test_update_banner_fixes.py -> 102 passed
  ./scripts/test.sh tests/test_api_timeout.py      -> 8 passed
  python3 scripts/ruff_lint.py --diff upstream/master -> 0 new violations
  Full ./scripts/test.sh -> 12137 passed, 18 failed (all 18 pre-existing
    flakes in unrelated subsystems; verified not v2.2 regressions)
2026-07-06 19:02:16 +00:00
..
__init__.py
agent_health.py fix(health): send Bearer auth on remote /health/detailed probe (#5418) 2026-07-02 16:24:39 +00:00
agent_sessions.py fix(#5587): keep active CLI sessions visible before final output 2026-07-05 01:25:50 +00:00
auth.py fix(#5578): stop login next= param from nesting into itself and exploding the URL 2026-07-05 00:12:19 +00:00
auth_oidc.py fix(#3825): harden oidc endpoint and claim validation 2026-06-30 20:59:19 -04:00
background.py
background_process.py fix(webui): render a server-initiated turn that finished during an SSE gap on a visible tab 2026-06-30 17:13:45 +00:00
clarify.py
commands.py fix(webui): guard non-dict resolve_moa_preset result in resolve_moa_config 2026-07-01 07:54:48 -05:00
compression_anchor.py
compression_recovery.py Live Stream: add compression recovery action 2026-07-04 21:42:32 +08:00
config.py fix(#5619): preserve same-path cfg overrides on reload 2026-07-05 17:08:20 -04:00
crash_visibility.py fix(webui): crash visibility — faulthandler + thread excepthook + exit audit (#4633) 2026-07-01 18:01:39 +00:00
dashboard_probe.py
extensions.py fix: prefer IPv4 for extension gallery downloads to prevent timeout on broken IPv6 2026-07-03 20:26:18 +00:00
gateway_chat.py fix(#5564): mint stable per-message ids so large-session forks align 2026-07-04 22:46:56 +00:00
gateway_restart.py fix(health): preserve restart failure contract and proofs (#5156) 2026-06-29 03:44:06 +00:00
gateway_watcher.py
goals.py
helpers.py fix(redaction): memoize deterministic API response redactor 2026-06-29 18:22:18 +00:00
kanban_bridge.py
metering.py
models.py Merge latest master into compression recovery action 2026-07-05 17:23:05 +08:00
oauth.py
office_documents.py #5142: office-doc preview + safe docx editing (#540), rebased on master; optional deps 2026-07-02 01:15:57 +00:00
onboarding.py feat(#3260): self-hosted provider setup (Ollama/LM Studio) in Settings (i18n complete) 2026-07-02 08:17:44 +00:00
passkeys.py
paths.py
plugin_providers.py
plugins.py
profiles.py fix(#5567): pin worker profile via hermes-agent v0.18.0 context-local HERMES_HOME override 2026-07-04 23:40:10 +00:00
providers.py feat(#3260): self-hosted provider setup (Ollama/LM Studio) in Settings (i18n complete) 2026-07-02 08:17:44 +00:00
request_diagnostics.py fix(#4973): replace per-request diagnostics Timer with a process-global watchdog thread 2026-06-26 07:58:13 +00:00
rollback.py
route_approvals.py fix(#5000): relay mirrored gateway approvals after stream loss 2026-06-27 06:03:24 +00:00
route_session_list_cache.py feat: add Webhooks sidebar filter parity 2026-06-30 17:06:31 +00:00
routes.py fix: drop auto-delete; manual-instruction only (PR #5688 round-3) 2026-07-06 19:02:16 +00:00
run_journal.py
runner_client.py
runtime_adapter.py
session_discoverability.py
session_events.py
session_export_html.py #4968: export chat to self-contained themed HTML, rebased on master 2026-07-02 02:36:09 +00:00
session_lifecycle.py fix(#5543): fire-and-forget commit_session_memory + drain-safe managed stop 2026-07-04 21:40:21 +00:00
session_ops.py fix(#5563): align forked context to display for large (compacted) sessions 2026-07-04 20:55:06 +00:00
session_recovery.py Merge latest master into compression recovery action 2026-07-05 10:23:15 +08:00
skill_usage.py
sse_chunked.py
startup.py
state_sync.py
streaming.py fix(streaming): guard empty terminal error hints 2026-07-06 11:15:31 +08:00
system_health.py fix: keep psutil optional for system health 2026-06-26 20:49:13 +00:00
terminal.py
todo_state.py
turn_journal.py
updates.py fix: drop auto-delete; manual-instruction only (PR #5688 round-3) 2026-07-06 19:02:16 +00:00
upload.py fix(auth): enforce active-profile session IDs 2026-06-30 04:28:15 +00:00
usage.py
webui_session_db.py fix: preserve compression recovery child markers in sidecar rebuild 2026-07-05 06:45:50 +08:00
workspace.py #5142: office-doc preview + safe docx editing (#540), rebased on master; optional deps 2026-07-02 01:15:57 +00:00
workspace_git.py
worktrees.py