Skip to content

fix(sdk): keep Windows OS probes and slow syncs off the session-index lock (#4544) - #4555

Merged
Yeachan-Heo merged 8 commits into
devfrom
fix/issue-4544-windows-index-lock
Aug 15, 2026
Merged

fix(sdk): keep Windows OS probes and slow syncs off the session-index lock (#4544)#4555
Yeachan-Heo merged 8 commits into
devfrom
fix/issue-4544-windows-index-lock

Conversation

@Yeachan-Heo

@Yeachan-Heo Yeachan-Heo commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Fixes #4544.

Problem (independently reproduced)

On Windows, gjc launches failed startup with:

Error: Failed to acquire lock for C:\Users\<user>\.gjc\agent\sdk\sessions\index.jsonl after 600 attempts

The lock owner was a live detached SDK broker holding index.jsonl.lock across a wedged Windows sync-family await inside the locked critical section. Stale-lock recovery correctly refused to remove the live owner (#652 discipline), so every later launch burned the full 600×100ms budget and crashed.

Deterministic baseline-red harness (Linux seam): a live process acquires the lock through the production withFileLock and awaits a never-settling promise inside the critical section; a second process runs production SessionIndex.open() → it exhausts all 600 attempts in 60.6s and crashes with exactly the reported error. Post-fix, the same permanent wedge still refuses to steal (correct) but fails with actionable owner diagnostics, and the realistic live-broker scenario (repeated slow-but-bounded locked passes) no longer starves a concurrent launch (LAUNCH-OK in <1s).

Root cause and fix (minimal, causal)

The machine-global lock must not be held across unbounded work. Three independent contributors, all fixed:

  1. OS probes under the lock. checkpointLiveHeartbeats called processIncarnation(pid) per live row while holding the machine-global lock; on win32 the fallback is Bun.spawnSync(powershell.exe …) — a synchronous OS spawn with no bound. projectIdentity (inside the locked reduceEvents) probed too, as did the own-pid derivation in append. All now run before the lock:
    • the heartbeat pass probes candidates from the last replayed history, then the locked section re-validates each row against the fresh replay plus the probed observation — a stale probe set can only skip one heartbeat cycle, never checkpoint a wrong host (fail-closed);
    • reduceEvents/projectIdentity accept a probed-incarnation map so the locked projection never probes the OS;
    • append derives the own-pid incarnation before taking the lock (the own pid is fixed for the process lifetime, so it cannot race anything).
  2. Unbounded PowerShell fallback. The win32 incarnation fallback is now bounded: timeout: 5000 with killSignal: "SIGKILL" enforcement (verified: Bun honors timeout/killSignal even against a TERM-ignoring child).
  3. Unattributable stalls. Every locked transaction now flows through one named choke point (withSessionIndexLock(operation, …)) that logs sdk broker: session index "<operation>" still holds the index lock after 10s — the exact operation is attributable from logs instead of surfacing as a bare 600-attempt crash. Lock-exhaustion errors now identify the holder: pid, liveness verdict, lock path, and the explicit never-displaced guarantee.

Stale-lock safety is unchanged: a proven-live owner's lock is never stolen; a dead owner is still reclaimed immediately.

Regression coverage

New packages/coding-agent/test/sdk-session-index-lock-contention.test.ts (deterministic, also routed into the windows-dev-doctor CI job via the affected-plan path list + workflow step):

  • live-owner lock retention without stealing, plus the new actionable diagnostics;
  • heartbeat-pass and append-path OS incarnation probes provably outside the lock-held section;
  • throw inside the critical section releases the lock (next op acquires immediately);
  • aborted acquisition fails fast instead of burning the retry budget;
  • concurrent launches converge behind a legitimate bounded holder (no corruption, healthy diagnose).

Also carries the mechanical regeneration of telegram-baseline-v1.json required by check:sdk-closure (pre-existing drift on exact dev HEAD: cf07293f74 added telegram-* tests without regenerating).

Verification

  • bun test packages/coding-agent/test/sdk-session-index-lock-contention.test.ts → 5 pass
  • bun test packages/coding-agent/test/sdk-session-index.test.ts packages/coding-agent/test/file-lock-gc-toctou.test.ts → 64 pass (×3 repeated runs)
  • bun test packages/coding-agent/test/sdk-broker*.test.ts (6 suites) → 155 pass; sdk-broker-lifecycle-e2e.test.ts → 82 pass
  • bun test packages/coding-agent/test/gc-runtime.test.ts gc-disk-retention.test.ts gc-e2e.test.ts → 81 pass
  • bun test scripts/ci-dev-affected.test.ts scripts/dev-ci-guard-topology.test.ts → 98 pass
  • bun --cwd=packages/coding-agent run check (biome + tsc) → clean
  • Baseline-red reproduced on unfixed dev (600 attempts / 60s / crash); post-fix scenario harness green.


[repo owner's gaebal-gajae (clawdbot) 🦞]

GJC verdict

gajae.pr-review-verdict.v1 merge-approved sha256:71b9faedf9593ee2244e42f555f43bcf612c3d0f2a7e6be5f75cb7a9c4ee7011 reviewer:human reviewer-id:probepark evidence:independent exact-head APPROVED review by probepark on c20a6725e7 (2026-08-15T05:18:33Z, commit_id-bound, write collaborator); delta byte-identical to the CI-green evidence set; leader re-verified lock-contention 10/10, session-index+file-lock 64/64, broker 44/44, lifecycle 144/144, ci-dev-affected 90/90, package check + fast gate clean
  • Target branch is dev
  • bun check passes
  • Tested locally
  • CHANGELOG updated
  • Verdict above matches the exact PR head, not an earlier commit

Rebase onto current dev — supersession receipt (head 0e34526865)

Dev advanced (#4563 merged, repairing the inherited Telegram daemon generation guard). The branch was reconstructed onto current dev:

  • Old base/head: 9d2a2d2f2d / a543a20469815c1e657678d19c81e2c3a25fff91 (digest sha256:d8073caec4f8…3542d) — probepark's APPROVED review at a543a20469 is superseded by the new head; a fresh exact-head approval is required.
  • New base/head: 64c15281691280be7854dac04baeb05188328ef4 / 0e34526865e035118df988eb39bc19059caad26a; new exact diff digest sha256:c8c2667959dee14f32ab9e294f386f43b85dbdf97fabb0a9938a1f2b37e5a3ef.
  • All 8 intended commits preserved (author clawdbot <clawdbot@users.noreply.github.com>, subject lines unchanged); the new base...head diff is byte-identical to the previously approved delta except packages/coding-agent/CHANGELOG.md context lines that only reflect dev's own SDK: terminal reconciliation expires before fire-and-wake consumption #4547 entry (verified by full-diff comparison).
  • Post-rebase verification (exact head, this worktree): sdk-session-index-lock-contention 10/10; sdk-session-index + file-lock-gc-toctou 64/64; sdk-broker family (restart/tail/notify/gc + core) 66 pass; sdk-broker-lifecycle-e2e 82/82; sdk-session-router-authority + gc-runtime 64/64; Windows session-path family 6 pass / 10 win32-gated skips; scripts/ci-dev-affected.test.ts 90/90 with all four PR source paths confirmed to route to the Windows session-path regression job; Telegram daemon generation guard --validate-current-tree exit 0 (the dev-inherited red is repaired by chore(guard): refresh discord/slack session-router digests after #4542 #4563) and guard tests 75/75; telegram-baseline-v1.json manifest 54/54 receipts green; bun --cwd=packages/coding-agent run check clean; verify-gjc-state-writers --fail clean.

@probepark — requesting your fresh independent review of exact head 0e34526865e035118df988eb39bc19059caad26a (base 64c1528169). @HaD0Yun — backup reviewer. One APPROVED review on 0e34526865 flips the verdict to merge-approved with the new digest.


[repo owner's gaebal-gajae (clawdbot) 🦞]

Rebase receipt #2 — dev advanced via #4540 (head c20a6725e7)

Dev advanced again (64c152816996e718a2b0f2a46cad4876bf922886966c6fd0e3, #4540 selection-order fixes; zero overlap with this PR's source files — only CHANGELOG context). All trial-merge/CI evidence bound to 0e34526865/base 64c1528169 is stale for terminal merge:

  • New base/head: 96e718a2b0f2a46cad4876bf922886966c6fd0e3 / c20a6725e78b8d12f179a96bae0c26952f849e80; new exact diff digest sha256:71b9faedf9593ee2244e42f555f43bcf612c3d0f2a7e6be5f75cb7a9c4ee7011.
  • All 8 commits preserved (author clawdbot), base…head delta identical modulo CHANGELOG context (verified by full-diff comparison against the 0e34526865 delta).
  • Exact-head local verification green: lock-contention 10/10; session-index + file-lock-gc-toctou 64/64; broker-restart + router-authority 44/44; ci-dev-affected 90/90; windows-path family 1 pass/8 win32-gated skips; Telegram guard --validate-current-tree exit 0; state-writers fast gate clean; package check clean.
  • CI run 31858127992 evidence (0e34526865) remains valid as product-behavior proof of the identical delta but is not terminal for merge; fresh CI is running on c20a6725e7.

@probepark / @HaD0Yun — the exact-head review request now targets c20a6725e78b8d12f179a96bae0c26952f849e80 (base 96e718a2b0). One APPROVED on that head flips the verdict to merge-approved with digest 71b9faed…e7011.


[repo owner's gaebal-gajae (clawdbot) 🦞]

Round-4 Codex finding (comment 3783532261) — fixed at 25093f5

P2 "Recheck probe freshness after replay" — accepted, fixed forward. The freshness bound was checked only at lock acquisition, but the cached observations are consumed after the awaited locked replay (#replayUnderLock() re-reads the whole log up to the 4 MiB rotation bound and fsyncs pending audit rows). A large index or a wedged Windows sync-family await can stretch the probe→write interval past the acquisition bound while the lock is held; a pid reused across that window would let alive(pid) see the replacement while the cached incarnation still matched the dead row — heartbeat for the wrong host.

Fix: the heartbeat pass rechecks freshness after the replay against a dedicated SESSION_INDEX_REPLAY_FRESHNESS_MS = 2s bound (sized for the full locked replay cost envelope of a healthy machine, not scheduler jitter) and fails closed — no heartbeat this cycle — before consuming the cached observations. The OS is still never probed under the lock; the next pass re-probes from scratch.

Regression test (sdk-session-index-lock-contention.test.ts): stretches the replay deterministically (clock jump at the locked log read); verified baseline-red at e6f8c926dd (heartbeat written without the recheck) and green with the fix (cycle writes nothing; following pass recovers normally).


[repo owner's gaebal-gajae (clawdbot) 🦞]

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a857b8a6dc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1328 to 1329
const current = probed.get(`${row.sessionId}\u0000${row.endpointGeneration}\u0000${row.pid}`);
if (current === undefined || current !== recordedIncarnation) continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Revalidate the incarnation after waiting for the lock

When lock acquisition is delayed, the PID observation in probed can be up to the full 60-second retry budget old. If the recorded process exits and its PID is reused after the probe but before this locked check, alive(row.pid) sees the replacement while this stale value still matches the former incarnation, causing a heartbeat to be appended for the wrong process and advertising the dead session as live for up to another heartbeat window. Revalidate the incarnation after contention or otherwise reject observations made before a delayed acquisition.

Useful? React with 👍 / 👎.

Comment on lines +545 to +549
const note = setTimeout(
() => logger.warn(`sdk broker: session index "${operation}" still holds the index lock after 10s`),
SESSION_INDEX_OP_SLOW_MS,
);
return withFileLock(logFor(agentDir), callback, SESSION_INDEX_LOCK_OPTIONS).finally(() => clearTimeout(note));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Start the slow-operation timer after lock acquisition

The timer starts before withFileLock has acquired anything, so whenever another process legitimately holds the index lock for more than 10 seconds, every waiting contender logs that its own named operation “still holds” the lock even though its callback has not started. This makes the new diagnostic attribute the stall to unrelated operations; arm and clear the timer inside the lock callback so it measures only actual critical-section duration.

Useful? React with 👍 / 👎.

Comment on lines +70 to +72
const incarnationModule = await import("../src/sdk/broker/process-incarnation");
const realProcessIncarnation = incarnationModule.processIncarnation;
const lockModule = await import("../src/config/file-lock");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Move the dynamic test imports to module scope

These executable await import(...) calls, repeated again later in this test, violate the repository's top-level-import-only contract. Replace them with module-scope namespace imports; vi.spyOn can operate on those namespaces without dynamically loading the modules inside each test.

AGENTS.md reference: AGENTS.md:L113-L116

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a3d93d08ac

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/coding-agent/CHANGELOG.md Outdated
- Managed fallback local snapshot failures now auto-recover with a bounded same-model retry (capped at `retry.maxRetries`) instead of terminating the turn: the discarded attempt is replay-safe and content-free, so the session re-issues the request without charging the provider fallback chain, advancing models, or mutating credentials. Exhausted retries still surface the explicit local diagnostic.

- Managed fallback local buffer overflows (`local_buffer_overflow`) now surface immediately with the original local diagnostic instead of entering the bounded `unknown` retry class: re-streaming the same request reproduces the same oversized response, and a local staging failure must never charge or advance the provider fallback chain, emit `model_fallback_switched`, or rotate credentials.
- Fixed Windows startup lock starvation in the SDK session index (#4544): a live detached broker holding `index.jsonl.lock` across a wedged Windows sync-family await exhausted every later launch's full 600-attempt lock budget. OS process-incarnation probes (which can spawn `powershell.exe` on Windows) now run before the machine-global index lock is taken in the heartbeat checkpoint pass and the own-pid registration derivation, the PowerShell fallback is time-bounded (`timeout` + `SIGKILL` enforcement), locked index transactions log an actionable slow-operation warning after 10s naming the exact operation, and lock exhaustion errors now identify the live owner (pid, liveness, lock path) instead of a bare attempt count. Stale-lock safety is unchanged: a proven-live owner's lock is never stolen.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore the displaced Unreleased entries

Adding this SDK entry also deletes the parent commit’s two existing Unreleased entries for managed snapshot retry recovery and local_buffer_overflow handling. If released in this state, both user-visible fixes disappear from the package release notes; retain those entries and add this one alongside them.

AGENTS.md reference: AGENTS.md:L188-L188

Useful? React with 👍 / 👎.

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

@probepark — requesting your independent review of exact head c485b6ba55 (base 8e0c0c1423).

This fixes #4544: a live detached Windows SDK broker held index.jsonl.lock across unbounded work, and every new gjc launch exhausted the full 600-attempt lock budget and crashed at startup.

Scope: 3 commits — the causal fix (OS incarnation probes moved before the machine-global index lock; bounded PowerShell fallback with SIGKILL enforcement; named slow-operation diagnostics; actionable lock-exhaustion errors naming the live owner), Windows tmpdir test portability, and the three Codex review findings (timer attribution inside the callback, stale-probe refresh, top-level imports).

Stale-lock safety is unchanged: a proven-live owner is never displaced. Deterministic suite sdk-session-index-lock-contention.test.ts (5 cases) + full local index/broker/lifecycle/gc suites green; bun check clean.

The PR body carries the required needs-human verdict line with the exact-head digest f9416068…5944d; on your approving review of this head I will flip it to merge-approved with the same digest and merge.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c485b6ba55

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1354 to +1357
probed.set(
`${row.sessionId}\u0000${row.endpointGeneration}\u0000${row.pid}`,
processIncarnation(row.pid),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not refresh probes while holding the index lock

When the unlocked probe batch itself takes over one second—for example, one Windows PowerShell probe reaches the new 5-second timeout, or several ordinary probes accumulate—probedAt was recorded before that batch, so this stale branch runs immediately after acquisition. It then calls synchronous processIncarnation for every candidate while holding the machine-global lock, recreating the starvation this change is intended to remove; enough slow probes can still make other launches exhaust their 60-second lock budget. Record completion after the batch and fail closed or release the lock before refreshing.

Useful? React with 👍 / 👎.

// stale incarnation still matched the dead process, checkpointing the
// wrong host. Refresh the observation set whenever contention delayed
// the pass; a fresh-enough set is kept as-is.
if (Date.now() - probedAt > SESSION_INDEX_LOCK_STALE_PROBE_MS) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject probes after any contended lock acquisition

The new one-second cutoff still trusts an observation after shorter contention. If the recorded process exits and its PID is reused during a 100–999 ms lock wait, alive(row.pid) observes the replacement while the cached incarnation still matches the former process, allowing a heartbeat for the dead session. The fresh evidence is this explicit threshold: revalidate or reject candidates whenever acquisition was delayed rather than treating observations under one second old as identity-safe.

Useful? React with 👍 / 👎.

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

@probepark — review request refreshed to exact head 7ac9174187 (base 8e0c0c1423). All four round-2 Codex findings are addressed there:

  • P1 (probe under lock): the stale-probe refresh is removed entirely. Identity evidence gathered before a delayed lock acquisition is discarded and the heartbeat cycle writes nothing (fail-closed). No code path probes the OS while holding the machine-global index lock anymore.
  • P2 (1s threshold still trusted short contention): replaced with an explicit freshness bound (SESSION_INDEX_PROBE_FRESHNESS_MS = 50) measured from probe-batch completion — uncontended acquisition + jitter passes, anything delayed fails closed.
  • P2 (displaced CHANGELOG entries): dev's two Unreleased entries (managed snapshot retry recovery, local_buffer_overflow) are restored ahead of this fix's entry.

Verification on this head: sdk-session-index ×5 repeated (no flake), lock-contention suite, sdk-broker/router/gc suites (182 pass), lifecycle e2e (82 pass), bun check clean, live-broker contention harness green.

PR body carries the single needs-human verdict with exact-head digest 112b000d…cc403; on your approving review of 7ac9174187 I flip it to merge-approved with the same digest and merge.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo
Yeachan-Heo force-pushed the fix/issue-4544-windows-index-lock branch from 7ac9174 to a2257a6 Compare August 14, 2026 12:10
@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

@probepark — review request updated after rebasing onto current dev: exact head a2257a69de (base e6f8c926dd, clean rebase, no conflicts, all four commits preserved).

Content unchanged from the reviewed state; all round-1 and round-2 Codex findings remain addressed. Re-verified post-rebase: lock-contention + session-index suites (52 pass), file-lock/broker-restart/router suites (60 pass), bun check clean.

Single verdict line carries needs-human with exact-head digest e4838dd7…cd1f2. Your approving review of a2257a69de flips it to merge-approved with the same digest; then I merge and close #4544.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a2257a69de

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 1352 to 1353
if (Date.now() - probedAt > SESSION_INDEX_PROBE_FRESHNESS_MS) return 0;
await this.#replayUnderLock();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recheck probe freshness after replay

When a large index or slow filesystem makes #replayUnderLock() exceed the 50 ms freshness window, this check has already passed, so a recorded process can exit and its PID can be reused during the awaited replay; the later alive(row.pid) then sees the replacement while probed still contains the former incarnation matching the dead row, allowing a heartbeat to be appended for the wrong host. The fresh evidence in this revision is that the freshness check now precedes the asynchronous replay; recheck after replay and immediately before consuming the cached observations.

Useful? React with 👍 / 👎.

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Exact-head evidence refreshed after fixing Codex round-4 finding (comment 3783532261):

  • New exact head: 25093f59d78a0576196d2c76d2fe0c0b80cf128e (deliberate repair commit on top of a2257a69de, same branch, intent preserved)
  • New digest: sha256:97df7d5be40363d212b38979cde1443ef750bbd25a59533375ccdb6dcfd32569 (recomputed over e6f8c926dd...25093f59d7, matches the verdict line exactly)
  • Verdict: still needs-human — the approval gate is honest; an independent reviewer must flip it to merge-approved

Round-4 P2 accepted and fixed: the probe-freshness bound was checked only at lock acquisition; the cached observations are consumed after the awaited locked replay, which re-reads the whole log (≤4 MiB rotation bound) and fsyncs audit rows. A pid reused across a slow replay could be checkpointed as the wrong host. The heartbeat pass now rechecks freshness after the replay (SESSION_INDEX_REPLAY_FRESHNESS_MS = 2s, sized for the healthy locked-replay envelope) and fails closed — no heartbeat this cycle; OS probes still never run under the lock.

Local verification on the new head: lock-contention suite 6 pass (new regression case proven baseline-red at e6f8c926dd); session-index + broker-restart + file-lock-gc-toctou 69 pass; broker lifecycle e2e 82 pass; bun --cwd=packages/coding-agent run check clean.

@probepark — independent review requested on exact head 25093f59d7 (base e6f8c926dd, clean, no conflicts). Your approving review flips the single verdict to merge-approved with digest 97df7d5b…32569; then I merge and close #4544.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 25093f59d7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +378 to +380
: probedIncarnations !== undefined
? probedIncarnations.get(probeKey)
: processIncarnation(latest.pid);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep unregister probes outside the index lock

On Windows when native incarnation lookup falls back to PowerShell, dead-registration cleanup still calls unregisterIfCurrent(), which holds the machine-global withFileLock and then calls listSessionIdentities() without a probe map; this fallback therefore runs the synchronous probe once per composite identity while the lock is held. With the new five-second per-probe timeout, enough indexed identities can still hold the lock beyond other launches' 60-second acquisition budget, reproducing the starvation this change is meant to fix. Pre-probe this path or provide a projection that does not perform OS liveness probes under the lock.

Useful? React with 👍 / 👎.

Comment on lines +341 to +342
if (info) {
const liveness = ownerLiveness(info.pid);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Report foreign lock owners without probing the local PID

When withFileLock is used with ownerHostId for the shared-volume topic registry and the lock record belongs to a different host, its PID is meaningful only on that remote host. This diagnostic nevertheless probes the same numeric PID locally and labels the foreign owner “live” or “dead but not reaped,” while omitting owner_host_id; a coincident local PID can therefore be misidentified as the holder and send the operator toward an unrelated process. Pass the requesting host identity into this helper and report foreign owners with unknown liveness instead.

Useful? React with 👍 / 👎.

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Exact-head CI classification receipt — head 25093f59d78a0576196d2c76d2fe0c0b80cf128e:

Superseded run 31810838937 (cancelled) — attributed, non-product cascade. Both runs are pull_request events on the same head: 31810838937 created 14:42:29Z (push), 31811214744 created 14:46:53Z (PR-body edit re-triggered the workflow); the workflow concurrency group cancelled the first in-flight run, which cascaded cancelled to native-build, the matrix shards, Windows dev:doctor + session-path regression, and Windows Telegram daemon safety, and the evidence producer correctly failed closed with required native build did not succeed. Plan artifact/digest valid and downloaded; Telegram daemon generation guard and all gjc-state-gates jobs passed on the cancelled run. No source patch warranted for this aggregate.

Fresh exact-head run 31811214744 is in progress on 25093f59d7. Already terminal: gjc-state-gates (static/read/runtime/integrity) success, affected-plan success, Telegram daemon generation guard success, PR contract bootstrap failure = the single honest needs-human verdict gate (Verdict needs-human intentionally blocks merge — no other diagnostic). Local trusted-root contract verification on this head agrees: bun scripts/verify-pr-verdict.ts --event reports exactly one ::error:: — the verdict gate; digest 97df7d5b…32569 matches, base ancestry and state-writer fast gate clean.

Standing requirement for MERGE_READY: native-build, Windows required lanes, all matrix shards, evidence producer + Affected path validation aggregate, and Virtual integration validation all terminal green on 25093f59d7, plus independent exact-head approval from probepark flipping the single verdict to merge-approved with the unchanged digest. Verdict remains needs-human with digest 97df7d5be40363d212b38979cde1443ef750bbd25a59533375ccdb6dcfd32569 until then.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Exact-head evidence refreshed after Codex round-5 findings (head 7171a4f7f78eee024bee8c64dea67d497c9f3a7f):

Round-5 triage — 2 real (fixed), 2 stale re-posts (already addressed):

  • P1 accepted, fixed — the conditional-unregister pass (unregisterIfCurrent) held the machine-global lock and projected every composite identity via listSessionIdentities(), whose unlocked projection probes the OS once per identity (powershell.exe on Windows). Probes now run before the lock and seed the locked projection; equality still compares against replayed events, so a stale observation can only refuse an unregister (fail-closed). New regression test proven baseline-red without the fix.
  • P2 accepted, fixedlockHolderDescription probed the numeric pid of a foreign owner_host_id record locally and labeled a coincident process live/dead. Foreign owners now report pid <n> on host <id> (liveness unknown from this host).
  • The session-index.ts:1366 and :1388 comments restate round-3/round-4 findings already addressed by the 50ms acquisition bound and the post-replay recheck; no change needed.

New digest: sha256:04c277b620f858b355aca9e188b3944a1da38b1c1b1b88f5cf6ef08a32cdae62 — single verdict line updated, still honest needs-human.

Local verification on the new head: lock-contention 7 pass (both new regression cases baseline-red at their unfixed sources); session-index + broker-restart + file-lock-gc-toctou 68 pass; router-authority 39 pass; gc suites 81 pass; broker lifecycle e2e 82 pass; bun --cwd=packages/coding-agent run check clean.

@probepark — independent review requested on exact head 7171a4f7f7 (base e6f8c926dd). Approving review flips the verdict to merge-approved with digest 04c277b6…dae62; then I merge and close #4544.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7171a4f7f7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (recordedIncarnation === undefined) continue;
probed.set(`${row.sessionId}\u0000${row.endpointGeneration}\u0000${row.pid}`, processIncarnation(row.pid));
}
const probedAt = Date.now();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Measure freshness from the earliest probe

When multiple sessions are eligible and a later Windows incarnation probe is slow or reaches its five-second timeout, the first cached observation may already be seconds old even though both freshness checks see an age near zero because probedAt is recorded only after the sequential batch. If that first process exits and its PID is reused while the remaining probes run, the locked pass sees the replacement as alive while the cached incarnation still matches the dead registration, so it emits a heartbeat that remains fresh for up to two heartbeat intervals. The fresh evidence in this revision is this post-batch timestamp; retain the batch start or a per-observation timestamp and reject observations that exceeded the freshness bound.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 309b23fb70

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1170 to +1173
for (const row of reduceEvents(this.#events, this.#policy.clock(), this.#agentDir).identities) {
const recordedIncarnation = row.hostIncarnation ?? row.processIncarnation;
if (recordedIncarnation === undefined) continue;
probed.set(`${row.sessionId}\u0000${row.endpointGeneration}\u0000${row.pid}`, processIncarnation(row.pid));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid probing every identity repeatedly before unregister

When Windows native incarnation lookup falls back to the synchronous PowerShell path, this enumeration already probes each identity inside reduceEvents (and probes each selected authority again while building sessions), after which line 1173 probes it yet again. The dead-registration sweep may invoke unregisterIfCurrent for up to 64 rows, so an index with many fallback identities performs this repeated full-index batch for every unregister, potentially blocking the broker event loop for minutes or hours despite moving the work outside the lock. Derive the probe targets without a liveness-projecting reduction, or capture and reuse each observation once.

Useful? React with 👍 / 👎.

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Cohort generation 2 — exact head 084f6e08b307065fa12ec319d781afc148472132 (digest sha256:f550324722ca814375263822ab9ed43305bc5fcc89dde0b20a0e1f66ce8fa325, verdict still honest needs-human):

The boundary review cohort (cleaner + architect + QA red-team) joined on the previous head and found one real defect plus review gaps, all fixed at this head:

  • QA red-team defect (fixed): both heartbeat freshness bounds used Date.now() — a backward wall-clock step (NTP slew, manual fix, VM snapshot restore) made the interval negative, both bounds passed, and a 70s-stale probe batch was consumed (reproduced: heartbeat written). The bounds now measure the monotonic clock (performance.now()), which never steps backward; a new regression test steps the wall clock back 70s while advancing the monotonic clock and proves the monotonic bound dominates. Baseline-red verified at the pre-fix head.
  • Architect P2 (fixed): unregisterIfCurrent used a raw withFileLock, bypassing the named slow-operation choke point (no 10s warning, 5s default retry budget instead of the 60s index budget). Now routed through withSessionIndexLock("conditional unregister", …).
  • Comment accuracy (fixed): the unregister probe-batch comment claimed staleness could 'refuse an unregister'; in truth the decision never consults the projected live flag — the observations are inert and exist only so the locked projection performs no OS probes. Comment now states exactly that.
  • Architect P3s (fixed): slow-op warning timer unref()ed; same-host lock diagnostic uses the full ownerIsAlive (pid + start-time identity) proof so a reused pid is not mislabeled (live); changelog aligned.
  • Test gaps (closed): 50ms acquisition-freshness branch now pinned (baseline-red); foreign-owner diagnostic test added earlier at 309b23fb70.

Local verification on this head: lock-contention 10 pass; session-index + broker-restart + file-lock-gc-toctou + router-authority 107 pass; gc suites 81 pass; broker lifecycle e2e 82 pass; bun --cwd=packages/coding-agent run check clean.

@probepark — independent review requested on exact head 084f6e08b3 (base e6f8c926dd). Your approving review flips the single verdict to merge-approved with digest f5503247…fa325; then I merge and close #4544.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 084f6e08b3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// on that host, so probing the same numeric pid here could mislabel a
// coincident local process as the holder. Report the owner host with
// unknown liveness instead.
if (info.owner_host_id !== undefined) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Compare the qualified owner with the requesting host

When withFileLock is called with ownerHostId, the presence of owner_host_id does not by itself mean the holder is foreign: a competing process on the same host writes the same ID, while an unqualified legacy/malformed record is deliberately treated as non-local by staleLockSnapshot. This branch therefore labels local live owners as remotely unverifiable and can still probe a coincidental local PID for an unqualified record on a shared volume. Pass the requester's ownerHostId into this helper and probe only when the two host IDs match.

Useful? React with 👍 / 👎.

@Yeachan-Heo
Yeachan-Heo force-pushed the fix/issue-4544-windows-index-lock branch from 084f6e0 to a543a20 Compare August 14, 2026 16:52
@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Rebase receipt — dev advanced (#4558 merged), branch rebased onto current dev:

Post-rebase verification on a543a20: lock-contention + session-index + broker-restart + file-lock-gc-toctou + config-cli → 94 pass; #4558 notification suites (inbound-acceptance/production-path/reaction-ordering/topic-registry/turn-ordering) → 101 pass; Windows session-path fsync + broker lifecycle e2e + router authority → 122 pass 1 skip (Windows-only skip); bun --cwd=packages/coding-agent run check (biome + tsc) clean; bun run build (tailwind + embedded client + compile) clean; worktree clean after build.

@probepark — fresh independent review request on exact head a543a20469 (base 9d2a2d2f2d). Your approving review flips the single verdict to merge-approved with digest d8073cae…3542d; then I merge and close #4544. Fresh CI is starting on this head.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Dependency-held — cross-PR integration dependency recorded (exact head a543a20469, base 9d2a2d2f2d):

Classification of the guard red (real product red, NOT attributable to this PR): the Telegram daemon generation guard fails on this head with four stale discord/slack SessionRouter digest attestations (#attach/#publishAttachment: manifest f01b84c3…/ecbcfe6d… vs actual d9e81077…/df250960…). Independently reproduced against pure dev: running bun scripts/telegram-daemon-generation-guard.ts --validate-current-tree with only dev's telegram-daemon-generation-manifest.json + session-router.ts checked out (zero #4555 changes) exits 1 with the identical four mismatches. The staleness was introduced by #4542's SessionRouter changes and surfaced for every cleanly-based PR after #4558 merged.

Exact repair lives in dedicated PR #4563 (head 06cbd1a150, one manifest-only generated commit, based on current dev): not duplicated or cherry-picked here — it keeps single ownership. Its own CI shows the guard green with only its honest needs-human verdict gate red, review in progress.

#4555 standing state: every other exact-head product job is green or still running (plan/gjc-state-gates/native-build/Windows lanes/shards); PR contract bootstrap fails with only the honest needs-human verdict. The branch remains cleanly based, all 8 intended commits preserved, digest d8073cae…3542d, verdict unchanged.

Terminal path: #4555 is dependency-held on #4563's terminal merge. The moment #4563 merges: rebase onto the new dev, rerun full exact-head CI, recompute the digest, refresh the needs-human verdict + signed evidence, and merge on independent exact-head approval + green + clean — then close #4544 and run current-dev verification. Any non-guard product failure on this PR is fixed forward immediately; none exists at this head.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Exact-head CI terminal decomposition — run 31821397603 on a543a20469 (28 success / 3 skipped / 4 failure). Every failure attributed, zero attributable to the #4555 diff:

Check Result Attribution
PR contract bootstrap failure Honest needs-human verdict gate — independent approval hold (by design)
Telegram daemon generation guard failure dev-inherited: #4542's stale discord/slack SessionRouter digests; reproduced on pure dev; repair owned by #4563
Affected path validation / evidence producer failure Fail-closed aggregation of the same guard (required Telegram daemon generation guard did not succeed)
Virtual integration validation skipped Structural: gated on affected.result == success (downstream of the same guard)

Green on this head: native-build, Windows dev:doctor + session-path regression, Windows Telegram daemon safety, check:@gajae-code/coding-agent, cli-smoke, all cargo builds, gjc-state-gates, plan. Skips are structural (darwin smoke plan-gated, Windows toolchain path-gated).

Dependency hold stands on #4563 terminal merge → then rebase, full CI rerun, digest refresh, merge on approval + green + clean.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Reviewer coverage closed + dependency/terminal product evidence — exact head a543a20469815c1e657678d19c81e2c3a25fff91:

Reviewer requests now explicit on GitHub (previously only named in the body): @probepark (primary, named in the verdict line) and @HaD0Yun (backup) — both carry write permission, so either can satisfy the exact-head approval gate. One APPROVED review on a543a20469 flips the single verdict to merge-approved with digest d8073cae…3542d.

Terminal product evidence at this head (CI run 31821397603):

Local exact-head product verification (this worktree, same head): lock-contention + session-index + broker-restart + file-lock + config-cli 94 pass; #4558 notification suites 101 pass (rebase preserved); fsync.windows + broker-lifecycle-e2e + router-authority 122 pass (1 Windows-only skip); check clean; build clean.

Standing dependency path (no change): #4555 is dependency-held on #4563's terminal merge → rebase onto new dev → full exact-head CI → digest refresh → fresh explicit reviewer request → merge only on approval + green + clean → close #4544 → current-dev verification.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@probepark probepark left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve at a543a2046

fix(sdk): keep Windows OS probes and slow syncs off the session-index path, head commit
"make probe freshness monotonic and close cohort review gaps". +736/-36 across 9 files.

Differential

# base 9d2a2d2f2, with this head test files applied
 90 pass  10 fail
  (fail) dev-ci canonical-plan workflow contract > routes Windows session-path regression suite onto windows-latest
  (fail) planTargetedTasks PR-mode targeting > routes Windows session-path regression for session I/O sources
  ... 8 more

# head a543a2046
 100 pass  0 fail
$ bun --cwd=packages/coding-agent run check   -> exit 0

Ten failures on base against ten behaviours. The two visible ones are CI-routing contracts - the
Windows session-path regression suite has to be scheduled onto a Windows runner and targeted in
PR mode - which is the right pairing for a change about keeping Windows probes off a hot path:
the fix is only meaningful if the Windows suite actually runs.

"Monotonic probe freshness" is the part I would have asked about had it not been in the title. A
freshness value that can go backwards is how a slow probe overwrites a newer result and reintroduces
the stall this removes; making it monotonic is the property that makes the off-path move safe rather
than merely faster.

merge-approved.

Reviewed by @probepark - method: fresh-worktree run and package typecheck at the exact head, separate clean base worktree with the head test files to prove the differential.

@Yeachan-Heo
Yeachan-Heo force-pushed the fix/issue-4544-windows-index-lock branch from a543a20 to 0e34526 Compare August 15, 2026 02:01
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Exact-head evidence — fresh current-dev reconstruction on PR #4555:

Supersession: head advanced a543a20469815c1e657678d19c81e2c3a25fff910e34526865e035118df988eb39bc19059caad26a; base advanced 9d2a2d2f2d64c15281691280be7854dac04baeb05188328ef4 (current dev, #4563 guard repair merged). Pushed with --force-with-lease proven against the still-a543a20469 remote.

  • All 8 intended commits preserved, author clawdbot <clawdbot@users.noreply.github.com> unchanged; base…head diff byte-identical to the approved delta except CHANGELOG context lines from dev's own SDK: terminal reconciliation expires before fire-and-wake consumption #4547 entry.
  • New exact diff digest: sha256:c8c2667959dee14f32ab9e294f386f43b85dbdf97fabb0a9938a1f2b37e5a3ef — verdict in the PR body regenerated for exactly this head; the prior probepark APPROVED review at a543a20469 is superseded and a fresh exact-head approval is requested.
  • Exact-head local verification: lock-contention 10/10; session-index + file-lock-gc-toctou 64/64; sdk-broker family 66 + lifecycle-e2e 82; router-authority + gc-runtime 64/64; Windows session-path family 6 pass / 10 win32-gated skips; ci-dev-affected 90/90 (all four PR source paths route to the Windows regression job); Telegram guard --validate-current-tree exit 0 and 75/75; baseline manifest 54/54; package check clean; state-writers fast gate clean.

@probepark — fresh independent review requested on exact head 0e34526865. @HaD0Yun — backup. Fresh CI is running on the new head; merge only on exact-head approval + green + merge-approved verdict, then close #4544.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo
Yeachan-Heo requested a review from probepark August 15, 2026 02:09
@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Terminal exact-head CI decomposition — run 31858127992 on 0e34526865e035118df988eb39bc19059caad26a (base 64c1528169): every product job green, sole failure is the honest needs-human verdict gate.

  • Windows dev:doctor + session-path regression: SUCCESS — the lane ran sdk-session-index-lock-contention.test.ts on real windows-latest: Ran 10 tests across 1 file, 0 fail.
  • Windows Telegram daemon safety: SUCCESS. Telegram daemon generation guard: SUCCESS (dev-inherited red repaired by chore(guard): refresh discord/slack session-router digests after #4542 #4563, as predicted).
  • Virtual integration validation: SUCCESS; native-build, plan, all four cargo builds, cli-smoke, ci-dry-run/selftest, yaml-parse, workflow-permissions, gjc-state-gates (static/read/runtime/integrity), affected test shards — all green.
  • PR contract bootstrap: FAILURE = the honest needs-human gate only: Verdict needs-human intentionally blocks merge. probepark's prior APPROVED is bound to stale head a543a20469 and cannot satisfy the contract; fresh exact-head approval requested from @probepark (primary) and @HaD0Yun (backup) — both carry write.

On a fresh APPROVED at 0e34526865 the single verdict flips to merge-approved with digest sha256:c8c2667959dee14f32ab9e294f386f43b85dbdf97fabb0a9938a1f2b37e5a3ef, the PR contract run turns green, and merge proceeds → close #4544.


[repo owner's gaebal-gajae (clawdbot) 🦞]

clawdbot added 8 commits August 15, 2026 02:44
… lock (#4544)

A live detached SDK broker holding <agentDir>/sdk/sessions/index.jsonl.lock
across a wedged Windows sync-family await starved every new gjc launch past
the full 600-attempt lock budget (~60s) and crashed startup. The stale-lock
recovery discipline (#652) is correct — a proven-live owner must never be
stolen from — so the fix bounds what the lock holder can do to the
machine-global critical section instead of weakening it.

Causal changes, each independently testable:

- OS process-incarnation probes now run BEFORE the machine-global index lock
  is taken: the heartbeat checkpoint pass probes candidates from the last
  replayed history, then the locked section re-validates each row against the
  fresh replay plus the probed observation (a stale probe set can only skip a
  heartbeat for one cycle, never checkpoint a wrong host — fail-closed). The
  own-pid registration derivation in append() moved before the lock the same
  way, and reduceEvents/projectIdentity accept a probed-incarnation map so the
  locked projection never probes the OS either.
- The Windows PowerShell incarnation fallback (Bun.spawnSync of powershell.exe)
  is bounded: timeout 5s with SIGKILL enforcement, so a wedged powershell can
  no longer block its caller — and the machine-global lock — indefinitely.
- Every locked session-index transaction now runs through one named choke
  point (withSessionIndexLock) that logs an actionable slow-operation warning
  after 10s identifying the exact operation still holding the lock, instead of
  the failure surfacing only as a bare lock-exhaustion crash on later
  launches.
- Lock exhaustion errors now carry bounded, actionable diagnostics: the live
  owner's pid, its liveness verdict, and the lock path, plus the explicit
  never-displaced guarantee — the user can act on the broker pid instead of
  only an attempt count.

Deterministic regression coverage (sdk-session-index-lock-contention.test.ts,
also routed into the Windows dev:doctor CI job via the affected-plan path
list): live-owner lock retention without stealing plus the new diagnostics,
heartbeat/append probes outside the lock-held section, throw-inside-critical-
section release, aborted-acquisition fail-fast, and concurrent launches
converging behind a legitimate bounded holder. The telegram baseline manifest
regeneration it carries is the mechanical sync required by check:sdk-closure.

Lore-id: issue-4544-windows-index-lock
Constraint: never steal a lock from a proven-live owner (#652 discipline)
Constraint: Windows durability (fsync/FlushFileBuffers) semantics unchanged
Rejected: stealing/force-releasing the live broker's lock | breaks #652 and corrupts concurrent index transactions
Rejected: dropping fsync on Windows | weakens durability; #4254 already fixed the read-only-handle EPERM
Rejected: timeout-interrupting locked index transactions | risks torn log/snapshot state mid-write
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: bun test packages/coding-agent/test/sdk-session-index-lock-contention.test.ts
Tested: bun test packages/coding-agent/test/sdk-session-index.test.ts packages/coding-agent/test/file-lock-gc-toctou.test.ts (x3 repeated)
Tested: bun test packages/coding-agent/test/sdk-broker*.test.ts (6 suites, 155 tests)
Tested: bun test packages/coding-agent/test/sdk-broker-lifecycle-e2e.test.ts (82 tests)
Tested: bun test packages/coding-agent/test/gc-runtime.test.ts gc-disk-retention.test.ts gc-e2e.test.ts
Tested: bun --cwd=packages/coding-agent run check (biome + tsc clean)
Not-tested: real windows-latest run (routed to windows-dev-doctor in dev CI)
On windows-latest TMPDIR is unset, so path.join(TMPDIR ?? "/tmp", …)
produced \tmp and every mkdtemp failed ENOENT before exercising any
product behavior — the Windows dev:doctor matrix (job 94749721733)
reported all five cases failing while the focused Linux run passed.

Use os.tmpdir() like the existing sdk-session-index-fsync.windows.test.ts
convention; no production code or assertion changes.

Lore-id: issue-4544-windows-test-portability
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: bun test packages/coding-agent/test/sdk-session-index-lock-contention.test.ts (5 pass)
Tested: bun test sdk-session-index.test.ts sdk-session-index-fsync.windows.test.ts file-lock-gc-toctou.test.ts
Not-tested: real windows-latest rerun (dev-ci will re-run it on this head)
Three review findings on the first fix, all accepted:

- Slow-op timer attribution (P2): the 10s warning timer armed before lock
  acquisition, so queueing time behind another legitimate holder was
  attributed to this operation's name. The timer now arms inside the lock
  callback and clears in its finally — it measures only actual
  critical-section duration.
- Stale incarnation observation (P2): a pid could exit and be reused
  between the pre-lock probe and the locked write (acquisition may queue
  behind another holder for up to the full retry budget), letting a stale
  observation that still matched the dead process checkpoint a heartbeat
  for the reused pid. Observations older than 1s at acquire time are now
  re-probed before any heartbeat is written.
- Test contract (P1): dynamic `await import(...)` calls in the new test
  violated the top-level-import-only contract; replaced with module-scope
  namespace imports (vi.spyOn works on the namespaces directly).

Lore-id: issue-4544-review-findings
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: bun test packages/coding-agent/test/sdk-session-index-lock-contention.test.ts (5 pass)
Tested: bun test sdk-session-index.test.ts file-lock-gc-toctou.test.ts sdk-broker-restart.test.ts sdk-session-router-authority.test.ts (107 pass)
Tested: bun test packages/coding-agent/test/sdk-broker.test.ts (62 pass)
Tested: bun --cwd=packages/coding-agent run check (biome + tsc clean)
Tested: live-broker contention harness (launch converges <1s behind bounded holder)
…tries

Second review round on the #4544 fix, all accepted:

- P1: the stale-probe refresh ran processIncarnation while HOLDING the
  machine-global lock — recreating the exact starvation this change
  removes whenever the unlocked batch itself was slow (e.g. one Windows
  probe hitting the 5s timeout). Removed entirely: identity evidence
  gathered before a delayed acquisition is now discarded and the cycle
  writes no heartbeat (fail-closed). The OS is never probed under the
  lock, in any path.
- P2: the 1s staleness threshold still trusted observations after short
  contention (100-999ms waits), where pid reuse would checkpoint a dead
  session as live. Replaced with an explicit freshness bound measured
  from probe-batch completion: uncontended acquisition + scheduler
  jitter passes, anything delayed fails closed.
- P2: the first round's CHANGELOG conflict resolution displaced dev's
  two existing Unreleased entries (managed snapshot retry recovery,
  local_buffer_overflow handling); both restored ahead of this fix's
  entry.

Lore-id: issue-4544-review-round2
Constraint: OS probes never run while the machine-global index lock is held
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: bun test sdk-session-index.test.ts x5 repeated (47 pass, no flake)
Tested: bun test sdk-session-index-lock-contention.test.ts file-lock-gc-toctou.test.ts (69 pass)
Tested: bun test sdk-broker*.test.ts sdk-session-router-authority.test.ts gc-*.test.ts (182 pass)
Tested: bun test sdk-broker-lifecycle-e2e.test.ts (82 pass)
Tested: bun --cwd=packages/coding-agent run check (biome + tsc clean)
Tested: live-broker contention harness green
Codex review round 4 on the #4544 exact head (comment 3783532261): the
probe-freshness bound was checked only at lock acquisition, but the
observation set is consumed after the awaited locked replay. A replay
re-reads the whole log (up to the 4 MiB rotation bound) and fsyncs
pending audit rows, so a large index or a wedged Windows sync-family
await can stretch the probe-to-write interval well past the acquisition
bound while the machine-global lock is held. A pid can exit and be
reused across that window; alive(pid) would see the replacement while
the cached incarnation still matched the dead row, checkpointing the
wrong host as live.

The heartbeat pass now rechecks freshness after the replay against a
dedicated replay bound (2s, sized for the full locked replay cost
envelope of a healthy machine instead of scheduler jitter alone) and
fails closed - no heartbeat this cycle - before consuming the cached
observations. The OS is still never probed under the lock; the next
pass re-probes from scratch. Regression test stretches the replay
deterministically (clock jump at the locked log read) and proves the
baseline-red: without the recheck the heartbeat is written; with it the
cycle writes nothing and the following pass recovers normally.

Lore-id: issue-4544-review-round4
Constraint: OS probes never run while the machine-global index lock is held
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: bun test sdk-session-index-lock-contention.test.ts (6 pass; new case baseline-red at e6f8c92)
Tested: bun test sdk-session-index.test.ts sdk-broker-restart.test.ts file-lock-gc-toctou.test.ts (69 pass)
Tested: bun test sdk-broker-lifecycle-e2e.test.ts (82 pass)
Tested: bun --cwd=packages/coding-agent run check (biome + tsc clean)
Codex review round 5 on the #4544 fix head:

- P1 (session-index.ts:380): the conditional-unregister pass held the
  machine-global lock and then projected every composite identity through
  listSessionIdentities(), whose unlocked projection probes the OS once
  per identity - on Windows a powershell.exe spawn under the lock, the
  exact starvation this change removes whenever enough identities are
  indexed. The probes now run before the lock is taken and seed the
  locked projection; the equality checks still compare against replayed
  events, so a stale observation can only refuse an unregister
  (fail-closed), never retire a live registration.
- P2 (file-lock.ts:342): a lock record carrying a foreign owner_host_id
  (shared-volume topic registry) belongs to another machine, but the
  exhaustion diagnostic probed the same numeric pid locally and labeled
  the coincident process live/dead, sending the operator toward an
  unrelated process. Foreign owners now report their host with unknown
  liveness instead.
- The remaining two round-5 comments restate round-3/round-4 findings
  already addressed (acquisition-bound freshness recheck and the
  post-replay recheck) and need no change.

Lore-id: issue-4544-review-round5
Constraint: OS probes never run while the machine-global index lock is held
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: bun test sdk-session-index-lock-contention.test.ts (7 pass; unregister case baseline-red without fix)
Tested: bun test sdk-session-index.test.ts sdk-broker-restart.test.ts file-lock-gc-toctou.test.ts (68 pass)
Tested: bun test sdk-session-router-authority.test.ts (39 pass) gc-*.test.ts (81 pass)
Tested: bun test sdk-broker-lifecycle-e2e.test.ts (82 pass)
Tested: bun --cwd=packages/coding-agent run check (biome + tsc clean)
Round-5 file-lock fix (#4544) shipped without a regression test for the
foreign owner_host_id branch of lockHolderDescription: a shared-volume
lock record owned by another host must report its owner host with
liveness unknown instead of probing the coincidental local pid and
labeling it live/dead. The test writes a foreign-host lock record whose
pid is the live local test process and asserts the exhaustion error
names the host, claims unknown liveness, and never labels the local
process the holder; the foreign owner's lock is never stolen either.

Verified baseline-red at the pre-fix parent 25093f5 (message labels
the local pid live) and green at this head.

Lore-id: issue-4544-review-round5
Constraint: foreign lock owners are never locally liveness-probed
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: bun test sdk-session-index-lock-contention.test.ts (8 pass)
Tested: baseline-red at 25093f5 (7 pass 1 fail on the new case)
Boundary cohort generation 2 on the #4544 change set:

- Red-team defect (real): both heartbeat freshness bounds measured the
  probe-to-write interval with Date.now(), a wall clock. A backward step
  (manual clock fix, NTP slew, VM snapshot restore) makes the interval
  negative, both bounds pass, and an arbitrarily stale observation batch
  is consumed - reproduced with a 70s-stale batch writing a heartbeat.
  The bounds now measure performance.now() (monotonic, never steps
  backward), so the fail-closed guarantee cannot be defeated by the
  system clock moving. Regression test steps the wall clock back 70s
  while advancing the monotonic clock past the replay bound and proves
  the monotonic advance dominates; baseline-red at the pre-fix head.
- Architect P2: unregisterIfCurrent used a raw withFileLock, bypassing
  the withSessionIndexLock choke point - no 10s named slow-operation
  warning and the 5s default retry budget instead of the 60s index
  budget. Routed through withSessionIndexLock('conditional unregister').
- Red-team comment inaccuracy: the unregister pre-lock probe batch does
  not 'refuse an unregister' on staleness - the decision never consults
  the projected live flag at all. The comment now states the truth: the
  observations are inert to the decision and exist only so the locked
  projection performs no OS probes.
- Architect P3s: the slow-op warning timer is unref()ed so it can never
  retain the process on an exit path; the same-host lock diagnostic uses
  the full ownerIsAlive proof (pid + start_time identity) so a reused
  pid is not mislabeled '(live)'; changelog wording aligned.
- Test gap: the 50ms acquisition-freshness branch was untested; a new
  regression test advances the monotonic clock inside lock acquisition
  and proves the cycle fails closed (baseline-red at the pre-fix head).

Lore-id: issue-4544-cohort-generation2
Constraint: freshness bounds are monotonic; OS probes never run under the lock
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: bun test sdk-session-index-lock-contention.test.ts (10 pass; wall-clock + acquisition cases baseline-red at 309b23f)
Tested: bun test sdk-session-index.test.ts sdk-broker-restart.test.ts file-lock-gc-toctou.test.ts sdk-session-router-authority.test.ts (107 pass)
Tested: bun test gc-runtime.test.ts gc-disk-retention.test.ts gc-e2e.test.ts (81 pass)
Tested: bun test sdk-broker-lifecycle-e2e.test.ts (82 pass)
Tested: bun --cwd=packages/coding-agent run check (biome + tsc clean)
@Yeachan-Heo
Yeachan-Heo force-pushed the fix/issue-4544-windows-index-lock branch from 0e34526 to c20a672 Compare August 15, 2026 02:46
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Dev advanced via #4540 (64c152816996e718a2b0f2a46cad4876bf922886966c6fd0e3); branch reconstructed with lease (0e34526865c20a6725e78b8d12f179a96bae0c26952f849e80), zero overlap with this PR's files.

  • New exact diff digest: sha256:71b9faedf9593ee2244e42f555f43bcf612c3d0f2a7e6be5f75cb7a9c4ee7011 — the single PR-body verdict is regenerated for exactly this head; everything bound to 0e34526865/64c1528169 (incl. CI run 31858127992) is superseded, not terminal.
  • All 8 commits preserved (author clawdbot); delta identical modulo CHANGELOG context.
  • Exact-head local verification: lock-contention 10/10; session-index + file-lock 64/64; broker-restart + router-authority 44/44; ci-dev-affected 90/90; Telegram guard exit 0; fast gate clean; package check clean.

@probepark (primary) / @HaD0Yun (backup): fresh exact-head review requested at c20a6725e7 (both re-requested on GitHub). Fresh Dev CI run 31860058461 is in progress on this head. Merge only on exact-head APPROVED + green + merge-approved verdict → close #4544.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Terminal exact-head CI decomposition — run 31860058461 on c20a6725e78b8d12f179a96bae0c26952f849e80 (base 96e718a2b0, current dev): every product job green; sole red is the honest needs-human verdict gate (no exact-head APPROVED yet).

Green at this head: native-build, plan, all cargo builds, cli-smoke, ci-dry-run/selftest, yaml-parse, workflow-permissions, gjc-state-gates (static/read/runtime/integrity), all affected test shards, Telegram daemon generation guard, Windows Telegram daemon safety, Windows dev:doctor + session-path regression (ran sdk-session-index-lock-contention.test.ts on windows-latest: Ran 10 tests across 1 file, 0 fail), Virtual integration validation.

@probepark @HaD0Yun — exact-head c20a6725e7 is fully green and awaiting one independent APPROVED review; the verdict then flips to merge-approved with digest sha256:71b9faed…e7011 and merge proceeds → close #4544.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

OWNER_CONFIRMATION_REQUIRED

  • Exact head: c20a6725e78b8d12f179a96bae0c26952f849e80 · base: current dev 96e718a2b0f2a46cad4876bf922886966c6fd0e3
  • Canonical diff digest: sha256:71b9faedf9593ee2244e42f555f43bcf612c3d0f2a7e6be5f75cb7a9c4ee7011
  • Exact-head CI: 28 success / 0 pending / 0 failure (incl. Windows session-path regression running the lock-contention suite, Windows Telegram daemon safety, Telegram generation guard, Virtual integration) — mergeable true
  • Exact-head authorized reviews: zero (probepark's prior APPROVED is bound to stale head a543a20469 and cannot satisfy the contract). Requested reviewers: @probepark (primary), @HaD0Yun (backup)
  • Verdict: needs-human — honest; no approval fabricated. One independent APPROVED review on c20a6725e7 flips it to merge-approved with the unchanged digest, the PR contract turns green, and merge proceeds → close Windows: SDK broker can hold session-index lock indefinitely and block startup after 600 attempts #4544.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@probepark probepark left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve at c20a6725e

Head moved since my approval of a543a2046; same change, rebased onto 96e718a2b. Re-verified
rather than assumed, since approvals are bound to the exact head:

$ bun test <the PR's touched suites>
 100 pass
 0 fail
$ bun --cwd=packages/coding-agent run check   -> exit 0

The substantive review stands: keeping Windows OS probes and slow syncs off the session-index path,
with monotonic probe freshness as the property that makes the move safe - a freshness value that can
go backwards would let a slow probe overwrite a newer result and reintroduce the stall this removes.

merge-approved at c20a6725e.

Reviewed by @probepark - method: fresh-worktree run and package typecheck at the exact head.

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Freshness dependency hold — current dev is red

This PR remains open at exact head c20a6725e78b8d12f179a96bae0c26952f849e80; its recorded base 96e718a2b0f2a46cad4876bf922886966c6fd0e3 predates current dev 290b31c110889f375c7713b3f64bd10c6338093e.

Current dev is not a valid green rebase target: push run 31867285336 fails the merge-induced Chrome default-root regression tracked by #4574. Existing exact-head product/review evidence is preserved but is not represented as current-base merge evidence. No unrelated rebase churn will be introduced onto a known-red base.

Disposition: item-specific hold remains active behind #4574. After #4574 merges and dev CI is green, this PR must be reconciled to that exact new dev head, with refreshed digest, CI, and review before any merge-ready claim.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Terminal retirement record — PR #4555 / issue #4544

Reproducible ownership and merge evidence for the SDK-session-reset re-establishment run.

Binding (all live-verified 2026-08-15, before and after mutation)

Artifact Value
PR #4555 fix(sdk): keep Windows OS probes and slow syncs off the session-index lock (#4544)
Merged head c20a6725e78b8d12f179a96bae0c26952f849e80 (8 commits, author clawdbot)
Recorded base 96e718a2b0f2a46cad4876bf922886966c6fd0e3
Diff digest sha256:71b9faedf9593ee2244e42f555f43bcf612c3d0f2a7e6be5f75cb7a9c4ee7011
Authorized review probepark APPROVED 4942943693, commit_id = c20a6725e7…, 2026-08-15T05:18:33Z, write collaborator (sole non-author approval; no self-approval)
Exact-head Dev CI run 31881501892 (#12638): 28 success / 2 skipped / 0 fail / 0 pending, terminal 11:34:55Z
Squash merge commit 804314081fe9d3f4d34014d1385f09d8d49a7116, dev HEAD, parent 45885ea287 (#4580), 2026-08-15T11:42:37Z
Linked issue #4544 closed COMPLETED, receipt comment 5302105847

Stale-run disposition (not rerun)

  • Validate exact-head PR contract runs 31866627443 (a1/a2) — fail on the frozen pull_request_review event whose body snapshot predates the 11:13:22Z verdict flip. Reruns replay the same frozen payload, so they cannot ever observe merge-approved. Superseded by a direct live validation: trusted-base validator (scripts/verify-pr-verdict.ts @ 96e718a2b0) against the live PR body + live reviews API → PR contract valid: merge-approved 71b9faed…, exit 0.
  • Dev CI 31860051735 (02:46Z) — cancelled by workflow concurrency; superseded by 31881501892 on the same head.

Live re-read before mutation (sole-ownership re-establishment)

PR state/head/base, all 30 check jobs of run 31881501892, review list filtered to exact head, issue #4574 state (closed, dev green again at 45885ea287 via #4575/#4580), current dev HEAD, mergeability (mergeable: true against current dev), and file-overlap analysis (only CHANGELOG.md touches dev@45885ea287 — dev-merge computed clean by GitHub, no rebase churn introduced).

Post-merge verification at 804314081f (dedicated worktree, this run)

  • bun test packages/coding-agent/test/sdk-session-index-lock-contention.test.ts → 10 pass / 0 fail
  • bun test packages/coding-agent/test/sdk-session-index.test.ts packages/coding-agent/test/file-lock-gc-toctou.test.ts → 64 pass / 0 fail
  • bun test packages/coding-agent/test/sdk-broker-restart.test.ts packages/coding-agent/test/sdk-session-router-authority.test.ts → 44 pass / 0 fail
  • bun test scripts/ci-dev-affected.test.ts → 90 pass / 0 fail
  • bun scripts/verify-gjc-state-writers.ts --fail → exit 0 (16/16 sites in the sanctioned writer)
  • bun --cwd=packages/coding-agent run check (biome + tsc) → exit 0
  • Post-merge Dev CI on 804314081f: run 31882739212 queued at receipt time

Constraints honored

No product-source modification in this run; no rebase onto the older known-red dev (290b31c110) — merge used the already-green 45885ea287 lineage; stale/cancelled runs left unrered; no release/tag/main mutation; remote branch fix/issue-4544-windows-index-lock left in place post-merge (merge-bound), worktree retired to detached 804314081f.


[repo owner's gaebal-gajae (clawdbot) 🦞]

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.

2 participants