Skip to content

fix(file-lock): name the Windows holder sidecar without a .json suffix - #6165

Closed
JasonBuildAI wants to merge 2 commits into
loopx-project:mainfrom
JasonBuildAI:codex/agent-k3f9-topic
Closed

JasonBuildAI wants to merge 2 commits into
loopx-project:mainfrom
JasonBuildAI:codex/agent-k3f9-topic

Conversation

@JasonBuildAI

Copy link
Copy Markdown
Contributor

Goal And Delivered Outcome

  • Outcome basis / optional anchor: issue [Bug]: ephemeral lock holder sidecars race directory walks and match *.json globs, causing Windows-only test flakes #6128 (reproduced on a native Windows host, commit 5c62c5c03).
  • Goal/source and gap: on Windows the lock-holder sidecar is named <lock>.json.lock.holder.json, so a *.json state walker over the locked directory harvests ephemeral lock metadata. External chat admission locks <request>.json and then enumerates sibling requests with ChatExternalConversations.pending() (glob("*.json")); it read the holder sidecar as a request and crashed at _admit_prepared with KeyError: 'binding_id'. The same name also races directory snapshots (a walker lists the holder, a concurrent lock deletes it, and the read fails).
  • Observable before → after, with the validation row that proves it: in test_exclusive_lock_persists_public_safe_holder_metadata, sorted(tmp_path.glob("*.json")) is ['state.json', 'state.json.lock.holder.json'] before the change and ['state.json'] after (regression_parity row). End to end, the external-requests directory holds ...json.lock.holder after admit, pending() returns only the real request, and the reported KeyError no longer fires (real_entrypoint row).
  • Issue/task and intended base: Closes [Bug]: ephemeral lock holder sidecars race directory walks and match *.json globs, causing Windows-only test flakes #6128; base main.

Author Declaration

  • Written by: an AI coding agent (Codex, OpenAI), at the human operator's direction and under their review. AI disclosure: this change was drafted with AI assistance at the operator's request, under their direction and review.

Implemented against

Criterion (spec clause) Disposition Symbol / path Test or command
The Windows holder sidecar must not be named like a state file (issue #6128 "Expected behavior") implemented loopx/file_lock.py lock_holder_path tests/test_file_lock.py::test_exclusive_lock_persists_public_safe_holder_metadata
*.json walkers must stop matching the holder sidecar (issue #6128 repro 1) implemented holder sidecar renamed <lock>.json.lock.holder same test, glob("*.json") assertion
External chat admission must not crash on the holder sidecar (issue #6128 third instance) implemented loopx/capabilities/native_chat/external_conversations.py pending() (unchanged; now receives no sidecar) real_entrypoint row below
The protocol document names the sidecar it describes implemented docs/reference/protocols/file-lock-acquisition-v0.md loopx check scan / review
POSIX single-file holder record is unchanged implemented lock_holder_path os.name == "nt" branch tests/test_file_lock.py, tests/test_file_lock_cross_process.py
  • Self-check before submission: read lock_holder_path, every reader (loopx/paths.py, incident/timeout code) and every literal .lock.holder.json reference; all readers go through lock_holder_path, so only the name-based *.json filters needed updating. Ran the focused and neighboring suites, the fail-before/mutation check, ruff, git diff --check, the public-boundary scan and the semantic-vocabulary advisory. Deliberately left out: repairing the unrelated downstream delegate authorization failure described in the gaps below, and the uv.lock artifact this host generated.

Scope And Continuation

  • Completed scope and remaining work: complete within this scope - the Windows holder sidecar no longer ends in .json, so no *.json walker can harvest it; the reported admission crash and the walker-race class are both removed at the one place that names the sidecar.
  • Slice boundary / successor: N/A. The issue's secondary suggestion (a shared walker helper that tolerates per-entry FileNotFoundError) is not needed for this defect: the sidecar is no longer a candidate for any *.json walk.

Validation

  • Tested revision: 275225121
  • Run state: finished
  • Input classes: synthetic
Check kind Result Public-safe evidence / limitation
regression_parity passed Same test, host-native Windows: with the old name assert sorted(tmp_path.glob("*.json")) == ["state.json"] fails (Left contains one more item: 'state.json.lock.holder.json'); with the change it passes. Mutation verified by re-adding the .json suffix to the sidecar name.
unit passed tests/test_file_lock.py tests/test_file_lock_cross_process.py: 18 passed, 2 skipped.
unit passed The touched turn suites: tests/test_loopx_turn_executor.py::test_reserved_managed_start_recovers_after_death_before_the_first_journal_write, ::test_run_once_rejects_corrupted_failed_turn_recovery_before_host_retry, tests/test_loopx_turn_driver.py::test_turn_run_once_cli_commits_distinct_host_guidance_without_task_step_edit: 6 passed.
real_entrypoint passed tests/extensions/test_lark_private_manager_returns.py::test_production_pump_returns_to_original_private_source_after_restart, host-native Windows with the Node runtime: on the base revision the fixture errors KeyError: 'binding_id' at external_conversations.py:87; on the head that error is gone and the directory contains ...json.lock.holder, with pending() returning only the real request. The test still fails later on this host for an unrelated reason (see gaps).
real_entrypoint passed Sidecar-scan contract: tests/test_collaboration_sidecar_scan.py tests/control_plane/test_legacy_coordination_writer_fence.py: 14 passed - the harness-simulated sidecar is still ignored by the collaboration inbox, and the writer-fence holder assertions (via lock_holder_path) still hold.
static passed ruff check on the changed files: all checks passed; git diff --check clean.
manual passed loopx check on the changed paths: public boundary scan clean: 6 files; scripts/generate_semantic_inventory.py --changed-from HEAD: no supported new vocabulary carriers.
  • Coverage and gaps: the change is one name and its name-based filters; the rows above exercise the lock holder record, the *.json glob, the external-request enumeration, the collaboration sidecar scan and the writer fence. One reported, unrelated failure remains and is disclosed rather than hidden: the end-to-end Lark fixture now reaches a downstream IndexError (transport.core.actions.store.list()[0]) on this host because the /delegate command settles as command_completed with failure_kind=ValueError ("original commission authorization is not currently valid") instead of producing a proposal - a host/environment authorization state in that harness, independent of the sidecar name. tests/control_plane/test_agent_lifecycle_state.py and tests/test_turn_lane_fence.py were reviewed (they read the holder only through lock_holder_path, so the rename is transparent) but could not finish inside this host's time budget; they belong in CI.

Frontend / Visual Evidence

  • UI impact: none
  • Before: N/A
  • After: N/A
  • States and viewports shown: N/A
  • Source data: none
  • Attention review: N/A - no user-visible surface changes.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring (no functional changes)
  • Documentation update
  • Test update

LoopX Area

  • Control plane (goals, todos, quota, scheduler, registry, runtime)
  • Benchmark boundary (adapters, runners, verifiers, scoring, evidence)
  • Capability or extension (providers, adapters, skills)
  • Public docs or presentation surface (README, protocols, dashboard)
  • Build, packaging, installer, or CI
  • Host or runtime integration

Technical Direction

  • Direction / acceptance reference, when applicable: N/A

Shared-authority RFC fixture impact

  • Production-scale fixture schema: N/A
  • Semantic dimensions changed, or reviewed no-impact rationale: N/A
  • Provider conformance arms run: N/A
  • Read-only legacy/file/PostgreSQL three-arm rehearsal (required for promotion, runtime-routing, or compatibility-projection changes): N/A

Boundary Checklist

  • Neither the diff nor this PR body/comments/attachments disclose private state, credentials, raw traces or verifier output, internal links, or local machine paths (including .loopx/, .codex/goals/, and live ACTIVE_GOAL_STATE.md).
  • I did not duplicate maintainer-owned benchmark work unless a maintainer split out a public issue for it.
  • I kept the change scoped to the linked issue/task.
  • I completed the visual evidence section for UI changes, or marked UI impact none.
  • Every commit includes a DCO Signed-off-by trailer (git commit -s).

The Windows lock-holder sidecar was named `<lock>.json.lock.holder.json`, so
any `*.json` state store over the locked directory matched it. External chat
admission locks `<request>.json` and then enumerates sibling requests with
`ChatExternalConversations.pending()` (`glob("*.json")`), which read the holder
metadata as a request and crashed at `_admit_prepared` with `KeyError:
'binding_id'` (issue loopx-project#6128). The same name also fed the lark delivery store,
which raised `KeyError: 'status'` while walking the same directory.

The sidecar is advisory lock metadata, not state, so drop the trailing `.json`
and keep `.holder` (`<lock>.json.lock.holder`). Every reader already resolves
the path through `lock_holder_path`, so only name-based `*.json` filters need
updating; the POSIX single-file holder record is unchanged.

Closes loopx-project#6128

Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
…walkers

The Windows holder sidecar lost its .json suffix, but a runtime root upgraded
from a release that wrote `<lock>.holder.json` still carries that file, and a
`*.json` state store reads it as a record: external chat admission then raised
KeyError `binding_id`, and the lark delivery store raised KeyError `status`
(issue loopx-project#6128).

Acquisition now best-effort deletes the legacy sibling while the lock is held,
and the three record walkers accept only the 24-hex request reference that
admission already enforces, matching the existing collaboration-inbox guard.

Verified: pending()/health()/pending_delivery_paths() raise KeyError on the
legacy sibling without these changes, and their new regression tests pass with
them.

Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
@JasonBuildAI

Copy link
Copy Markdown
Contributor Author

Closing this PR: it duplicates the earlier, equivalent PR #6164 (opened 2026-10-10T18:59Z, 11 minutes before this one), which fixes the same root cause in the same files (loopx/file_lock.py, loopx/capabilities/native_chat/external_conversations.py) and also covers the legacy <lock>.holder.json upgrade path.

Under the coordination rules for this contribution batch (an earlier equivalent PR takes the topic; a later agent must not keep a second PR for the same fix), this PR yields and is closed rather than pushed forward. No maintainer action is needed on it.

The branch codex/agent-k3f9-topic is left in place as a record of the work; the head commit is 96955af.

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.

[Bug]: ephemeral lock holder sidecars race directory walks and match *.json globs, causing Windows-only test flakes

1 participant