Skip to content

fix(tests): retain exact SDK broker fixture owners#2265

Merged
Yeachan-Heo merged 1 commit into
devfrom
fix/issue-2246-broker-orphan-teardown
Jul 15, 2026
Merged

fix(tests): retain exact SDK broker fixture owners#2265
Yeachan-Heo merged 1 commit into
devfrom
fix/issue-2246-broker-orphan-teardown

Conversation

@Yeachan-Heo

Copy link
Copy Markdown
Owner

Summary

  • add a fixture-only, provenance-bound SDK broker lease tied to the exact retained child/incarnation
  • make notification, ACP, lifecycle, production-host, and detached harness cleanup phase-major, serialized, retryable, and root-preserving on failure
  • add bounded no-recreation checks plus Linux/macOS/Windows Dev CI regression coverage

Rejected designs

  • ambient broker-startup bypass variables
  • discovery PID, process-name, or process-group cleanup
  • operator-state/process cleanup
  • shared-symlink lifecycle authority

Verification

  • focused SDK/notification/harness regression suite: 134 pass, 0 fail
  • repeated lifecycle/default-model/ACP fixture subset: two consecutive clean passes
  • targeted production-host adapter and Slack worker paths: pass
  • bun run --cwd packages/coding-agent check: pass
  • bun run build: pass
  • signed commit: fc490531efad20a49f254761fac837d6ccaf793e

Closes #2246.


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

@Yeachan-Heo
Yeachan-Heo force-pushed the fix/issue-2246-broker-orphan-teardown branch from fc49053 to 2314089 Compare July 15, 2026 01:00

@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: fc490531ef

ℹ️ 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".

}

function linkWorkspacePackages(scopeDir: string, packages: LinkedWorkspacePackage[]): string[] {
function linkWorkspacePackages(scopeDir: string, packages: LinkedWorkspacePackage[]): void {

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 Restore repo node_modules links for harness env

When createHarnessCliEnv(fakeRepo, ...) is used by the existing packages/coding-agent/test/harness-control-plane/cli.test.ts cleanup-contract test, this helper now only creates links in the temporary NODE_PATH tree and no longer creates/records the repoRoot/node_modules/@gajae-code/* symlinks the test immediately lstats. That makes the unchanged test fail with ENOENT and also removes the fallback source-package resolution path for harness CLI subprocesses in uninstalled worktrees; please restore the repo-node_modules link/marker behavior or update all callers/tests to the new contract.

Useful? React with 👍 / 👎.

const started = await withFixtureBrokerEnvironment(() =>
startFixtureBrokerWithLeaseForTest({ agentDir, env: linked.env }),
);
const rootCleanup = createFixtureRootCleanup(agentDir, agentDir, started.lease);

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 Clean the whole harness fixture root

For harness tests that call createHarnessCliEnvWithFixtureBroker(repoRoot, root), the afterEach paths no longer remove root directly and rely on this fixture cleanup. Registering agentDir as the cleanup root only deletes <root>/agent, leaving the fake tmux logs/server state and root-registry under <root> after every successful test; pass the outer fixtureRoot here so the root-wide cleanup actually removes the owned harness tree once the broker lease is closed.

Useful? React with 👍 / 👎.

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Terminal hostile review — CLEAR / APPROVE

Exact head: 23140897f0663d4672b17a046d29a8c30446036c

  • Lease provenance: fixture authority is minted only for the initiating fresh local broker child/incarnation; external/prior/in-flight join paths fail closed.
  • Serialized phase-major cleanup: all runtime shutdown/dispose phases settle before the single root lease closes; concurrent cleanup shares one attempt.
  • Failure retry authority: callbacks, keys, required-owner classification, lease, root, and phase diagnostics remain retained until verified success; setup cleanup failures expose retryFixtureCleanup.
  • Root preservation: root removal follows verified exact-child exit and a bounded no-recreation observation; delayed recreation resets removal/absence for retry.
  • Isolation: no ambient broker-disable seam, discovery PID/name/group cleanup, operator-state/process cleanup, or shared-symlink lifecycle authority. Production GJC_SDK_DISABLE semantics remain unchanged and fixture setup sanitizes/restores it locally.
  • Cross-platform path: Dev CI includes relevant-gated Linux, macOS Intel, and Windows broker-fixture regression legs.

Current exact-head evidence: 136 focused tests pass with 609 assertions; coding-agent check/type/canonicalization passes; repository build passed; signed commit verified. Independent hostile Architect verdict: CLEAR / APPROVE, blockers: none.

CI is still settling; merge remains gated on every exact-head required check reaching terminal success.


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

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Exact-head CI found one platform-specific blocker on 23140897f0663d4672b17a046d29a8c30446036c: the new Windows matrix leg lacked a valid TMPDIR, so portable tests fell back to \tmp, and the sanitized broker child environment omitted required Windows process variables. No ownership or cleanup invariant failed.

Fixed in signed commit 7a943c223 by binding TMPDIR/TEMP/TMP to runner.temp and preserving the minimal Windows runtime allowlist (SYSTEMROOT, COMSPEC, PATHEXT, WINDIR, temp variables) in fixture child environments. Local broker/cleanup regressions (43/43) and coding-agent check pass. The prior terminal architecture verdict remains CLEAR; merge is now gated on the new exact head reaching all-green CI.


[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: 66cdd1c8c2

ℹ️ 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 .github/workflows/dev-ci.yml Outdated
Comment on lines +47 to +48
broker-fixture-lease:
name: Broker fixture lease regression / ${{ matrix.platform }}

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 Gate the broker fixture job in the aggregate

This added matrix job is not included in the stable Affected path validation aggregate, whose needs and result checks only cover affected-plan, affected-native, and affected-shards. In the branch-protection setup described by this workflow comment, a Linux/macOS/Windows broker-fixture regression can fail without failing the required aggregate status, so the new coverage may be bypassed unless this job is added to the aggregate dependency/result check or otherwise made required.

Useful? React with 👍 / 👎.

key: "acp-subprocess",
requiredOwner: "runtime-and-broker",
shutdown: () => teardown(oracle),
dispose: () => oracle.drainStderr(),

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 Do not drain stderr after failed teardown

When teardown(oracle) hits its documented failure path where the ACP subprocess is still alive after SIGKILL, cleanupFixtureRoot still runs the dispose phase after the failed shutdown, and this callback awaits stderrDrain, which only resolves once the child's stderr fd closes. In that stuck-child scenario the cleanup hangs until the test timeout instead of surfacing the teardown error and preserving the root; make this drain conditional on confirmed exit or add a bounded wait.

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: 4df72e1f64

ℹ️ 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 208 to 209
await cleanupFixtureRoot(cleanup);
authStorage.close();

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 Close AuthStorage before removing the fixture root

When this test runs on Windows (the new broker-fixture matrix includes windows-latest), cleanupFixtureRoot(cleanup) recursively removes cwd, but authStorage is still open on cwd/testauth.db until the next line. Windows commonly refuses to delete open SQLite/database files, so cleanup can fail and retain the broker/root; close or register authStorage before removing the fixture root.

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: 5bddc3d441

ℹ️ 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".

const agentDir = path.join(repo, ".gjc", "agent");
const stateRoot = path.join(repo, ".gjc", "state");
const environment = createFixtureBrokerEnvironment(repo, agentDir);
const fixtureSessionDir = path.join(agentDir, "sessions", "-");

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 Save lifecycle transcripts in the default session dir

When invokeScenario reaches session.resume or session.fork, the lifecycle broker validates the supplied transcript by inventorying SessionManager.getDefaultSessionDir(cwd, agentDir) in validateSavedTranscript, but this fixture now writes the source session under the hard-coded agentDir/sessions/-. For the temp repos created here, the default directory is an encoded -tmp-... path, so the saved session is absent from the broker inventory and the shipped mcp/daemon lifecycle topology tests fail with invalid_input instead of exercising resume/fork.

Useful? React with 👍 / 👎.

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Successor repair pushed at db8be54afd981b58b600ac49b9d7575d43a8c62d: hardens shared broker fixture teardown across the four failing test surfaces. Exact-head Dev CI has not attached yet, so merge remains held pending the full Windows/Darwin/Linux matrix and fresh hostile review.\n\n—\n*[repo owner's gaebal-gajae (clawdbot) 🦞]*

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Exact-head repair pushed as signed commit db8be54afd981b58b600ac49b9d7575d43a8c62d and fresh Dev CI run 29385164480 is executing: https://github.com/Yeachan-Heo/gajae-code/actions/runs/29385164480

Failure classification and repair:

  • Windows job 87251605915: verified EBUSY after exact lease close while removing fixture roots. Root removal now retries only transient filesystem-lock errors within the same retained cleanup phase; lease/runtime authority remains verified and is never reacquired or replayed. A deterministic injected-EBUSY regression proves three removal attempts and one lease close.
  • shard-1 job 87252679962: the failing harness assertion still expected the rejected shared-repository symlink lifecycle. The regression now proves two isolated NODE_PATH link roots clean independently and a pre-existing repository link remains untouched; no shared-symlink authority was restored.
  • Darwin job 87251605930: both exact contender children remained alive while full CLI bootstrap exceeded the test's 5-second readiness window. The process regression now uses the production 10-second discovery budget, fails early if both contenders exit before publication, and always awaits exact-child exit before root deletion. It still requires one winner, one reaped loser, discovery bound to the winner, and zero surviving children after teardown.

Validated local evidence on the successor diff:

  • broker + cleanup suite: 44 pass, 0 fail
  • portable notification/default-model group: 39 pass, 0 fail
  • concurrent raw-process startup stress: 20/20 serial passes
  • clean agent-root-isolated affected shard 1/8: 1504 pass, 69 skip, 0 fail
  • coding-agent Biome, SDK canonicalization, and TypeScript checks: pass
  • git diff --check: pass

The earlier local shard timeout cascade was environment/process contamination: another live runner was concurrently executing sdk-host-wiring.test.ts against the same ambient agent root. The file passes alone (35/35), and the full shard passes with only the agent root isolated; no test was skipped or weakened.

PR remains open and unmerged pending exact-head adversarial review and terminal green required checks.

[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: db8be54afd

ℹ️ 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".


export function createFixtureBrokerEnvironment(root: string, agentDir: string): NodeJS.ProcessEnv {
const environment: NodeJS.ProcessEnv = {
HOME: root,

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 Set Windows home variables for fixture children

When these fixture environments are used on Windows, setting only HOME does not pin os.homedir() for the spawned broker/session-host processes; Bun/Node use USERPROFILE on win32 (docs), and otherwise fall back to the real user profile. Any fixture child path that expands ~ or uses os.homedir() can therefore read or write outside the owned fixture root, defeating the cleanup/isolation this helper is adding; set USERPROFILE (and preferably HOMEDRIVE/HOMEPATH) to the fixture root as well.

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: 68d945afab

ℹ️ 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 crates/gjc-sdk/src/server.rs Outdated
Comment on lines +829 to +830
while !self.accept_task.is_finished() || !self.state.connections.lock().is_empty() {
tokio::task::yield_now().await;

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 Track pre-handshake sockets before returning

When a TCP client is accepted but stalls before completing the WebSocket handshake, accept_loop has already spawned handle_conn, but that task does not insert into state.connections until after accept_hdr_async_with_config and the hello write succeed. In that state this loop can see the aborted accept task as finished and an empty connection map, so stop_and_wait() returns while an accepted socket task is still alive, breaking the teardown boundary that the JS cleanup now relies on. Track spawned connection tasks (or insert/mark them before the handshake) instead of using only the post-handshake connection map.

Useful? React with 👍 / 👎.

@Yeachan-Heo
Yeachan-Heo force-pushed the fix/issue-2246-broker-orphan-teardown branch from 68d945a to 43f9282 Compare July 15, 2026 04:10

@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: 43f92829b3

ℹ️ 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".

}
for (const [id, pending] of this.#pending)
this.#settlePending(id, pending, new SdkClientError("connection_closed", "SDK client closed"));
await Promise.all([...transports].map(incarnation => this.#closeTransport(incarnation)));

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 Share the in-flight close wait

With this change close() can remain pending until #closeTransport receives the socket close event, but #closed is set before that await. If a second teardown path calls and awaits close() while the first close is still in progress, it returns immediately and may remove the state/root while the WebSocket is still open; keep and return a shared close promise until the transport wait finishes.

Useful? React with 👍 / 👎.

Comment thread packages/bridge-client/src/client.ts Outdated
return;
}
if (Number(socket.readyState) === WebSocket.CLOSED) resolve();
await promise;

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 Bound the WebSocket close wait

When the peer stops completing the close handshake, for example a half-open or stalled SDK client, #closeTransport now waits solely for a close event with no timeout or error fallback. Any caller that awaits SdkClient.close() during shutdown can hang indefinitely; add a bounded fallback or abort path so close remains a deterministic cleanup operation.

Useful? React with 👍 / 👎.

Comment thread crates/gjc-sdk/src/server.rs Outdated
}
}
cancel.cancel();
while connections.join_next().await.is_some() {}

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 Bound connection joins during server shutdown

In shutdown, the accept loop now waits for every connection task without a cap. If a client has stopped reading while a connection task is already awaiting send_msg or drains queued direct frames after stop(), cancellation cannot interrupt that await, so stop_and_wait() can hang forever instead of releasing the fixture/state root; abort or time-bound remaining connection tasks after cancellation.

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: 13d3fdd2ad

ℹ️ 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 +1850 to +1851
if (ownerReleaseFailures.length > 0) {
throw new AggregateError(ownerReleaseFailures, `SDK notification runtime ${id} owner release failed.`);

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 Keep failed runtime release retryable

When rt.host.stop(), snapshot close, or rt.server.stopAndWait() throws during a fixture session_shutdown, this new throw correctly fails the cleanup once, but stopSession already deleted the runtime from runtimes before these awaits. A subsequent cleanupFixtureRoot retry calls the same shutdown handler, sees no runtime, and marks the phase verified without retrying the failed host/server release, so the fixture root can be removed while those owners are still open; preserve the failed runtime or keep a retryable release handle until all owner-release phases verify.

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: bd5ada2602

ℹ️ 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".

let serverStopped = false;
try {
rt.server.stop();
await rt.server.stopAndWait();

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 Preserve host stop proof across server-stop retries

With the new cleanupRetries path, when the first teardown attempt succeeds in rt.host.stop() but this stopAndWait() call throws, the retained retry no longer has a started host: the next rt.host.stop() returns "already", so recordStop(... hostStopped: hostStopped && serverStopped) never marks rollback.hostStopped true even after the server retry succeeds. Lifecycle cleanup proof explicitly requires rollback.hostStopped in packages/coding-agent/src/sdk/broker/lifecycle.ts, so a transient server-stop failure can leave an otherwise completed retry reported as incomplete; carry forward the successful host-stop state or treat "already" on a cleanup retry as released.

Useful? React with 👍 / 👎.

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Owner-direct recovery update at 2fc1c1f1b:

  • origin/dev (b86930b4c) does not contain the exact-owner broker teardown repair, so Test SDK broker owners survive fixture teardown as detached orphans #2246 remains unresolved on current dev.
  • Preserved and committed the focused fixture repair: exact-path closeModelCache() ownership plus runtime cleanup registration before fixture-root removal.
  • Focused verification: bun test packages/ai/test/model-cache.test.ts packages/coding-agent/test/notifications-session-switch.test.ts packages/coding-agent/test/sdk-default-model-selection-e2e.test.ts15 pass, 0 fail.
  • Package check: bun run --cwd packages/ai check → Biome clean and TypeScript clean.

Hostile review requested: please attack teardown ordering, retry-authority retention, exact-path cache ownership, root recreation, cross-platform behavior, and any operator-owned state/process risk before merge.

@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: 2fc1c1f1be

ℹ️ 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".

expectedRuntime?: SessionRuntime,
): Promise<boolean> {
const requestedRuntime = runtimes.get(id);
const requestedRuntime = runtimes.get(id) ?? cleanupRetries.get(id);

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 Check cleanup retries before replacing runtimes

When a teardown failure stores the old runtime in cleanupRetries, a subsequent session_start for the same session id can still create a replacement because startup only checks runtimes; after that, this lookup always selects the new runtimes entry and the retained failed runtime is no longer reachable by an ordinary retry. In that restart-before-retry scenario the fixture/root cleanup can stop the replacement and proceed while the original failed owner remains open, so the retry map needs to fence startup or address the retained runtime before any replacement with the same id.

Useful? React with 👍 / 👎.

@Yeachan-Heo
Yeachan-Heo force-pushed the fix/issue-2246-broker-orphan-teardown branch from 2fc1c1f to 2ed46e7 Compare July 15, 2026 07:42
@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Rebased the repair onto current origin/dev (b86930b4c) and force-pushed with lease. Current head: 2ed46e736.

Post-rebase proof:

  • bun run build:native → native SDK addon rebuilt successfully.
  • cargo check -p pi-natives → pass.
  • bun run --cwd packages/ai check → pass.
  • bun test packages/ai/test/model-cache.test.ts packages/coding-agent/test/notifications-session-switch.test.ts packages/coding-agent/test/sdk-default-model-selection-e2e.test.ts packages/coding-agent/test/fixture-broker-cleanup.test.ts packages/coding-agent/test/sdk-host-wiring.test.ts70 pass, 0 fail.
  • Worktree clean; local and remote branch heads match.

Hostile review remains requested against the rebased head.

@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: 2ed46e7368

ℹ️ 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".

let serverStopped = false;
try {
rt.server.stop();
await rt.server.stopAndWait();

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 Add stopAndWait to the native compatibility gate

When a checkout or installed package loads a stale @gajae-code/natives addon with the same package version but built before NotificationServer.stopAndWait existed, startup still passes assertNativeRuntimeCompatibility because native-runtime-compatibility.ts only checks registerArbitratedAsk/retireIfUnclaimed. The first shutdown then reaches this call and throws TypeError: rt.server.stopAndWait is not a function, which turns ordinary session cleanup into a retained owner-release failure; include stopAndWait in the runtime capability check or keep a safe fallback to stop().

Useful? React with 👍 / 👎.

@Yeachan-Heo
Yeachan-Heo force-pushed the fix/issue-2246-broker-orphan-teardown branch from edae6bb to 9fef6c5 Compare July 15, 2026 08:02
@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Fresh hostile exact-head review requested for 9fef6c5d17f98d1c55657394216768ebf691da61.

Windows CI evidence addressed:

  • notifications-session-switch.test.ts failed on Windows Server 2025 with EPERM: operation not permitted, fsync after the gate-store rename. The repair treats EPERM as unsupported only for the Windows directory durability barrier; file fsync EPERM remains fail-closed. A deterministic injected-win32 regression covers both branches.
  • sdk-default-model-selection-e2e.test.ts hit its 4s SDK request timeout and Bun 5s test timeout; Bun then killed the dangling process and exact-owner teardown encountered EBUSY removing the root. The fixture now has a 10s SDK request budget and 30s file test budget so registered session/model-cache/broker owners finish cleanup before root removal.

Fresh-head verification:

  • focused 4-file Windows regression/lifecycle suite: 43 pass, 0 fail
  • bun run --cwd packages/coding-agent check: pass
  • worktree clean; pushed with force-with-lease

Please review exact head only and attack directory-vs-file fsync classification, interrupted cleanup, retry authority, root recreation, and operator-state safety.

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

@codex review

Hostile exact-head review requested for 40e1a8bbb3a321d8516bdec2cf35d34bd1f2d3ea only.

The previous win32 head still failed because sdk-default-model-selection-e2e.test.ts timed out inside the six-file shared Bun process, while the exact same test passed in its isolated affected-path job. This head gives the fixture owner its own Bun process before the notification group, preventing cross-file global/native owner contention from interrupting shutdown and producing the secondary EBUSY root-removal failure.

Attack whether process isolation hides a real owner leak, whether exact session/model-cache/broker authority still closes before root removal, and whether any operator-owned state can be touched. Do not rely on reviews of older commits.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: 40e1a8bbb3

ℹ️ 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".

@Yeachan-Heo
Yeachan-Heo force-pushed the fix/issue-2246-broker-orphan-teardown branch from 40e1a8b to de55d42 Compare July 15, 2026 11:45
@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Rebased PR #2265 onto current origin/dev at 26bb02e724f971b3c7927637fb68967fea4a6ae0. Successor exact head: de55d4273f59f854d4927646824a68456eff8d0b.

Supersession check:

Semantic rebase resolutions preserved both sides:

  • fix(sdk): stabilize dev lifecycle shard #2292 lifecycle handling plus exact fixture owner cleanup;
  • current Windows atomicity aggregate gate plus the broker-fixture matrix gate;
  • current notification controller plus failed-runtime retry authority;
  • all six notification-config sessions are registered exact cleanup owners.

Validation on this exact tree:

  • isolated default-model fixture: 1 pass / 36 assertions
  • notification fixture group: 38 pass / 108 assertions
  • broker + cleanup + gate group: 63 pass / 187 assertions
  • sdk-host/default-model/session-switch/gate conflict surfaces: 83 pass / 379 assertions
  • notification-config/default-model/cleanup semantic suite: 38 pass / 193 assertions
  • workflow YAML parse: pass
  • affected CI selector tests: 35 pass / 151 assertions
  • coding-agent Biome, SDK canonicalization, and TypeScript: pass
  • worktree clean; force-pushed with lease

@codex review

Hostile exact-head review requested for de55d4273f59f854d4927646824a68456eff8d0b only. Attack semantic conflict resolution, owner retry retention, root-removal ordering, Windows process isolation, and any possibility of touching operator-owned state. Merge remains blocked on fresh exact-head required CI.

@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: de55d4273f

ℹ️ 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 +75 to +79
HOME: root,
XDG_CONFIG_HOME: path.join(root, "config"),
GJC_AGENT_DIR: agentDir,
GJC_CODING_AGENT_DIR: agentDir,
PI_CODING_AGENT_DIR: agentDir,

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 Pin Windows home variables to the fixture root

For Windows fixture children, setting only HOME does not isolate code that calls os.homedir(): Node/Bun use USERPROFILE when present, otherwise the current user's profile directory. The broker/CLI children created with this env import code such as packages/utils/src/env.ts, which eagerly reads path.join(os.homedir(), ".env"), so the new Windows fixture matrix can still read the runner/operator profile instead of the temp root. Please set Windows home/data variables such as USERPROFILE, LOCALAPPDATA/APPDATA, and preferably HOMEDRIVE/HOMEPATH to paths under root rather than relying on HOME alone.

Useful? React with 👍 / 👎.

cleanupRetries.set(id, rt);
throw new AggregateError(ownerReleaseFailures, `SDK notification runtime ${id} owner release failed.`);
}
cleanupRetries.delete(id);

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 Retain stale cleanup retries across same-id restarts

When owner cleanup fails, the old runtime is stored in cleanupRetries; however, if the same session id is started again before that retry succeeds, stopSession() will pick the new runtime from runtimes and this unconditional delete removes the old failed runtime's retry authority after the new runtime stops successfully. In that same-id restart scenario the original leaked owners are no longer reachable for retry, so this should only delete when cleanupRetries.get(id) === rt (and/or block startup until the retained cleanup completes).

Useful? React with 👍 / 👎.

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Head de55d4273 is invalidated by failed required job 87343682844. Successor exact head: 98b9230156366ca75bd2ae72882e729f4715f5a6.

Diagnosis:

  • exact CI and local branch both failed bridge-client-sdk-package-smoke with Export named SecretInput not found in @gajae-code/tui;
  • a detached clean worktree at current origin/dev (26bb02e724) reproduced the identical failure, proving this was a current-dev SDK smoke packaging regression rather than the broker-owner repair;
  • current dev feat(tui): move notification setup & daemon recovery into settings (#2050) #2088 added SecretInput to coding-agent/TUI, but the smoke packed AI, bridge-client, and coding-agent only, allowing Bun to install the older registry TUI package.

Minimal repair:

Exact local validation:

  • bun packages/coding-agent/scripts/build-sdk-package-smoke.ts → pass (root: 594, sdk: 39)
  • affected selector tests → 35 pass / 151 assertions
  • coding-agent Biome, SDK canonicalization, and TypeScript → pass
  • worktree clean; pushed with force-with-lease

@codex review

Review 98b9230156366ca75bd2ae72882e729f4715f5a6 only. Prior exact-head reviews and CI are stale. Attack whether the smoke now exercises matching release dependency boundaries without hiding registry compatibility failures, and re-review the broker fixture changes against the new exact head. Merge remains blocked on fresh required CI.

@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: 98b9230156

ℹ️ 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".


// Failed terminal teardown remains fenced from normal runtime lookup while the
// exact runtime object retains authority for an explicit idempotent retry.
const cleanupRetries = new Map<string, SessionRuntime>();

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 Keep failed cleanup fenced before restarting

When a session shutdown fails after runtimes.delete(id), this map is the only remaining owner of the failed runtime, but startSession still checks only sessionStartPromises/runtimes before creating a replacement for the same session id. In that retry/restart scenario the old server/host remains alive in cleanupRetries, the new runtime shadows it because stopSession reads runtimes.get(id) ?? cleanupRetries.get(id), and the exact failed cleanup owner can no longer be retried. Treat cleanupRetries as a startup fence or drain it before allowing a replacement runtime.

Useful? React with 👍 / 👎.

Comment on lines +47 to +48
const code = (error as NodeJS.ErrnoException).code;
if (process.platform !== "win32" || (code !== "EPERM" && code !== "EACCES")) throw error;

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 Do not ignore file fsync failures

syncFile is used for both the temp broker.json file and the parent directory, so this Windows EPERM/EACCES exception now also treats a failed temp-file sync as success. If a permissions/AV/share-mode failure happens while syncing the just-written file, writeBrokerDiscovery will still rename and publish a discovery record whose contents were not durably flushed; only the directory durability barrier should be optional on Windows, e.g. by splitting the helper or passing an explicit directory-sync flag.

Useful? React with 👍 / 👎.

@Yeachan-Heo

Copy link
Copy Markdown
Owner Author

Exact head 98b923015 and all reviews/CI against it are invalid. Successor exact head: 177ea778697afb8be6e5c7f0ce4b8dc399fb1d03.

Deduplicated validity matrix

Candidate Severity Verdict Successor repair / evidence
Windows fixture env inherited USERPROFILE/APPDATA/LOCALAPPDATA/HOMEDRIVE/HOMEPATH P1 VALID All five are fixture-owned; hostile ambient-value regression added.
cleanupRetries allowed same-id replacement/shadowing and could lose exact retry owner P1 VALID Exact runtime is retained for the entire terminal release window; same-id startup is fenced; stop lookup/deletion is identity-checked.
Successful host-stop proof could be lost when server stop failed and cleanup retried P1 VALID Per-runtime host/server/broker-release proof survives retries; proven releases are not replayed.
Discovery syncFile() swallowed Windows EPERM for both file and directory fsync P1 VALID File fsync is always fail-closed; only the directory durability barrier tolerates unsupported Windows EPERM/EACCES.
Concurrent SdkClient.close() callers did not share teardown P1 VALID One retained close promise is returned to every caller.
WebSocket/client close could wait forever P1 VALID Close wait is deadline-bounded with deterministic timeout error; stalled and concurrent fake-transport regressions added.
Native connection task joins could wait forever P1 VALID One-second graceful join, then abort-and-drain; focused Rust bound test added.
Native compatibility did not require stopAndWait P1 VALID Capability gate now requires it; missing-capability regression added.
Atomicity contract expected old aggregate needs list P1 CI VALID Contract now requires broker-fixture-lease dependency and result gate.
Windows process-heavy Q10 fixture sent duplicate invalid inherit control before advertised selections P2 VALID duplication/starvation trigger Removed from this fixture; exact invalid-input coverage remains in sdk-host-wiring.test.ts. Q10 fixture now tests advertised selections and owner teardown only.

Exact Windows CI facts folded in

  • notification atomicity failed solely on the stale aggregate topology assertion;
  • broker/cleanup prelude passed 44/44;
  • Q10 fixture timed out on the duplicate invalid request, then cleanup hit EBUSY;
  • aggregate failed only because those two required gates failed.

Successor verification

  • blocker regression matrix: 81 pass / 396 assertions
  • exact broker CI sequence after native rebuild: 45 pass + 1 pass + 38 pass, 0 fail
  • exact atomicity contract: 1 pass / 26 assertions
  • coding-agent check/type/canonicalization: pass
  • bridge-client check/type: pass
  • workflow YAML parse: pass
  • native addon build: pass
  • Rust bounded join test: pass
  • cargo check -p pi-natives: pass
  • worktree clean; pushed with force-with-lease

@codex review

Review 177ea778697afb8be6e5c7f0ce4b8dc399fb1d03 only. Re-attack every matrix row, especially exact retry identity, file-vs-directory durability, close timeout semantics, native abort/drain, Windows profile isolation, and whether removal of the duplicate invalid request leaves exact coverage in sdk-host-wiring. Do not rely on older reviews. Merge remains blocked on fresh required CI.

@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: 177ea77869

ℹ️ 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/bridge-client/src/client.ts Outdated
Comment on lines +629 to +630
const timeoutMs = this.#remainingTimeout();
if (timeoutMs <= 0) throw this.#deadlineError();

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 skip socket close after elapsed deadline

When a client is constructed with an absolute deadline and an active socket is still open after that deadline elapses, close() now computes timeoutMs as 0 and throws before it ever calls socket.close(). This makes common cleanup paths like finally { await client.close(); } after a timed-out request leave the WebSocket open with its client-side listeners removed, so the transport can leak until process exit; close should still attempt teardown and use a separate close grace/default instead of the expired operation deadline.

Useful? React with 👍 / 👎.

Yeachan-Heo pushed a commit that referenced this pull request Jul 15, 2026
Reconstructs the minimal still-needed exact-owner SDK broker fixture
teardown fix onto current origin/dev, resuming PR #2265. The detached
SDK broker spawned for a fixture is now reaped by targeting the exact
owned child (pid + incarnation), with a bounded SIGTERM -> SIGKILL ->
verified-exit reap and a single-use fixture lease, so a fixture never
orphans its broker owner or leaks its root.

Core:
- src/sdk/broker/ensure.ts: exact-owner reap (reapSpawnedBroker),
  pid+incarnation owner fencing, single fixture lease.
- src/sdk/broker/discovery.ts: broker process incarnation.
- src/sdk/bus/index.ts: retained terminal-cleanup proof for idempotent
  owner release across retries.
- src/sdk/bus/native-runtime-compatibility.ts + crates (gjc-sdk server,
  pi-natives): bounded stop_and_wait/stopAndWait joining half-open and
  established connections idempotently.
- ai/model-cache.ts: close the shared cache only when it owns the exact
  db path. bridge-client: idempotent close awaiting exact socket owners.
- test/helpers/fixture-broker-cleanup.ts (new): root-wide phased fixture
  teardown (shutdown/dispose, lease close, root removal + verified
  absence). Wired into sdk-production-host and sdk-lifecycle-fixture.

Supersession audit vs origin/dev (dropped from the original PR):
- workflow-gate-broker.ts + test: Bun Windows fsync EPERM now handled by
  #2309.
- .github/workflows/dev-ci.yml + dev-ci-notification-atomicity CI hunk +
  build-sdk-package-smoke TUI-pack: CI/workaround churn.
- sdk-public-surface manifest + CHANGELOG entries: unrelated churn.
- #2292 is already in this fix's base (built upon, not superseded);
  #2305 (Discord) touches no file in this set.

Repro (#2246): sandboxed operator-safe harness (owned pid only) shows
the detached fixture broker alive + root present before teardown and
reaped + root absent after fixtureRootCleanup.
@Yeachan-Heo
Yeachan-Heo force-pushed the fix/issue-2246-broker-orphan-teardown branch from 177ea77 to c871404 Compare July 15, 2026 22:06

@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: c87140419e

ℹ️ 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 +206 to +207
await retireLiveFixtureOwner();
await cliEnv.cleanup();

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 Close the fixture broker after retirement failures

When retireLiveFixtureOwner() throws (for example, the harness owner remains live after the bounded retirement wait), this afterEach exits before cliEnv.cleanup() closes the fixture broker lease created in beforeEach. That leaves the pre-started broker child running after the test has already failed; preserve the diagnostic root if needed, but close the broker lease in a finally/aggregate cleanup path so the failure does not orphan the fixture broker.

Useful? React with 👍 / 👎.

Comment on lines +222 to +224
await retireLiveFixtureOwner();
sdkServer.stop(true);
await cliEnv.cleanup();

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 Close CLI fixtures on retirement errors

If retireLiveFixtureOwner() fails here (e.g. harness retire fails or the owner is still live), control jumps to the catch block, which only stops the fake SDK server and never runs cliEnv.cleanup(). Since beforeEach now starts a fixture broker with an exact lease, that failure path leaks the broker process; close the fixture lease in a finally path even when preserving the harness roots for inspection.

Useful? React with 👍 / 👎.

Yeachan-Heo pushed a commit that referenced this pull request Jul 15, 2026
Reconstructs the minimal still-needed exact-owner SDK broker fixture
teardown fix onto current origin/dev, resuming PR #2265. The detached
SDK broker spawned for a fixture is now reaped by targeting the exact
owned child (pid + incarnation), with a bounded SIGTERM -> SIGKILL ->
verified-exit reap and a single-use fixture lease, so a fixture never
orphans its broker owner or leaks its root.

Core:
- src/sdk/broker/ensure.ts: exact-owner reap (reapSpawnedBroker),
  pid+incarnation owner fencing, single fixture lease.
- src/sdk/broker/discovery.ts: broker process incarnation; temp
  broker.json fsync is fail-closed (syncFile) while only the directory
  barrier (syncDirectory) tolerates win32 EPERM/EACCES.
- src/sdk/bus/index.ts: retained terminal-cleanup proof; cleanupRetries
  fences same-id startup so a replacement never shadows an unreaped owner.
- src/sdk/bus/native-runtime-compatibility.ts + crates (gjc-sdk server,
  pi-natives): bounded stop_and_wait/stopAndWait joining half-open and
  established connections idempotently.
- ai/model-cache.ts: close the shared cache only when it owns the exact
  db path. bridge-client: idempotent close() that always issues
  socket.close() under a dedicated close grace (never gated on an expired
  request deadline), so a transport is never leaked on close.
- test/helpers/fixture-broker-cleanup.ts (new): root-wide phased fixture
  teardown wired into sdk-production-host and sdk-lifecycle-fixture.

Exact-head hostile-review repairs:
- bridge-client close() no longer throws on an elapsed operation deadline
  before socket.close(); dedicated close grace + leak regression test.
- Regression tests assert temp broker.json fsync stays fail-closed (even
  for EPERM) and the directory-barrier tolerance is win32-scoped only.
- Confirmed cleanupRetries already fences same-id startup (owner-release
  proof preserved across retry).

Supersession audit vs origin/dev (dropped from the original PR):
- workflow-gate-broker.ts + test: Bun Windows fsync EPERM now via #2309.
- .github/workflows/dev-ci.yml + dev-ci-notification-atomicity CI hunk +
  build-sdk-package-smoke TUI-pack: CI/workaround churn.
- sdk-public-surface manifest + CHANGELOG entries: unrelated churn.
- #2292 is already in this fix's base (built upon); #2305 (Discord)
  touches no file in this set.

Repro (#2246): sandboxed operator-safe harness (owned pid only) shows the
detached fixture broker alive + root present before teardown and reaped +
root absent after fixtureRootCleanup.
@Yeachan-Heo
Yeachan-Heo force-pushed the fix/issue-2246-broker-orphan-teardown branch from c871404 to 4eab6fa Compare July 15, 2026 22:29

@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: 4eab6fa03a

ℹ️ 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".

observed,
stop: async () => {
const fixtureEnv = createFixtureBrokerEnvironment(agentDir, agentDir);
return withFixtureBrokerEnvironment(async () => {

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 Keep the production host from double-registering SDK

When this helper is used, withFixtureBrokerEnvironment clears GJC_SDK_DISABLE while createAgentSession runs. In that environment createAgentSession still appends the built-in SDK/notifications extension for top-level sessions (packages/coding-agent/src/sdk/session.ts around the shouldHostSdk(...) branch), so the explicit factory below is registered a second time. The later auto runtime can overwrite the endpoint file, causing callers to connect to an uninstrumented host with no onSdkRequest observations while two servers run for one session; keep the env change scoped to broker startup or restore the old disable guard around session creation.

Useful? React with 👍 / 👎.

Yeachan-Heo pushed a commit that referenced this pull request Jul 15, 2026
Reconstructs the minimal still-needed exact-owner SDK broker fixture
teardown fix onto current origin/dev, resuming PR #2265. The detached
SDK broker spawned for a fixture is now reaped by targeting the exact
owned child (pid + incarnation), with a bounded SIGTERM -> SIGKILL ->
verified-exit reap and a single-use fixture lease, so a fixture never
orphans its broker owner or leaks its root.

Core:
- src/sdk/broker/ensure.ts: exact-owner reap (reapSpawnedBroker),
  pid+incarnation owner fencing, single fixture lease.
- src/sdk/broker/discovery.ts: broker process incarnation; temp
  broker.json fsync is fail-closed (syncFile) while only the directory
  barrier (syncDirectory) tolerates win32 EPERM/EACCES.
- src/sdk/bus/index.ts: retained terminal-cleanup proof; cleanupRetries
  fences same-id startup so a replacement never shadows an unreaped owner.
- src/sdk/bus/native-runtime-compatibility.ts + crates (gjc-sdk server,
  pi-natives): bounded stop_and_wait/stopAndWait joining half-open and
  established connections idempotently.
- ai/model-cache.ts: close the shared cache only when it owns the exact
  db path. bridge-client: idempotent close() that always issues
  socket.close() under a dedicated close grace (never gated on an expired
  request deadline), so a transport is never leaked on close.
- test/helpers/fixture-broker-cleanup.ts (new): root-wide phased fixture
  teardown wired into sdk-production-host and sdk-lifecycle-fixture.
- scripts/build-sdk-package-smoke.ts: pack the matching local @gajae-code/tui
  so the SDK package smoke exercises the workspace release boundary instead
  of falling back to an older registry tui missing the SecretInput export
  (fixes bridge-client-sdk-package-smoke; pre-existing on dev).

Exact-head hostile-review repairs:
- bridge-client close() no longer throws on an elapsed operation deadline
  before socket.close(); dedicated close grace + leak regression test.
- Regression tests assert temp broker.json fsync stays fail-closed (even
  for EPERM) and the directory-barrier tolerance is win32-scoped only.
- Confirmed cleanupRetries already fences same-id startup (owner-release
  proof preserved across retry).
- Restored the SDK package-smoke TUI-pack fix after CI proved the smoke
  regresses without it.

Supersession audit vs origin/dev (dropped from the original PR):
- workflow-gate-broker.ts + test: Bun Windows fsync EPERM now via #2309.
- .github/workflows/dev-ci.yml + dev-ci-notification-atomicity CI hunk:
  CI topology churn coupled to the dropped workflow.
- sdk-public-surface manifest + CHANGELOG entries: unrelated churn.
- #2292 is already in this fix's base (built upon); #2305 (Discord)
  touches no file in this set.

Repro (#2246): sandboxed operator-safe harness (owned pid only) shows the
detached fixture broker alive + root present before teardown and reaped +
root absent after fixtureRootCleanup.
@Yeachan-Heo
Yeachan-Heo force-pushed the fix/issue-2246-broker-orphan-teardown branch from 4eab6fa to 216a8c6 Compare July 15, 2026 22:36
Yeachan-Heo pushed a commit that referenced this pull request Jul 15, 2026
Reconstructs the minimal still-needed exact-owner SDK broker fixture
teardown fix onto current origin/dev, resuming PR #2265. The detached
SDK broker spawned for a fixture is now reaped by targeting the exact
owned child (pid + incarnation), with a bounded SIGTERM -> SIGKILL ->
verified-exit reap and a single-use fixture lease, so a fixture never
orphans its broker owner or leaks its root.

Core:
- src/sdk/broker/ensure.ts: exact-owner reap (reapSpawnedBroker),
  pid+incarnation owner fencing, single fixture lease.
- src/sdk/broker/discovery.ts: broker process incarnation; temp
  broker.json fsync is fail-closed (syncFile) while only the directory
  barrier (syncDirectory) tolerates win32 EPERM/EACCES.
- src/sdk/bus/index.ts: retained terminal-cleanup proof; cleanupRetries
  fences same-id startup so a replacement never shadows an unreaped owner.
- src/sdk/bus/native-runtime-compatibility.ts + crates (gjc-sdk server,
  pi-natives): bounded stop_and_wait/stopAndWait joining half-open and
  established connections idempotently.
- ai/model-cache.ts: close the shared cache only when it owns the exact
  db path. bridge-client: idempotent close() that always issues
  socket.close() under a dedicated close grace (never gated on an expired
  request deadline), so a transport is never leaked on close.
- test/helpers/fixture-broker-cleanup.ts (new): root-wide phased fixture
  teardown wired into sdk-production-host and sdk-lifecycle-fixture.
- scripts/build-sdk-package-smoke.ts: pack the matching local @gajae-code/tui
  so the SDK package smoke exercises the workspace release boundary instead
  of falling back to an older registry tui missing the SecretInput export
  (fixes bridge-client-sdk-package-smoke; pre-existing on dev).

Exact-head hostile-review repairs:
- bridge-client close() no longer throws on an elapsed operation deadline
  before socket.close(); dedicated close grace + leak regression test.
- Regression tests assert temp broker.json fsync stays fail-closed (even
  for EPERM) and the directory-barrier tolerance is win32-scoped only.
- Confirmed cleanupRetries already fences same-id startup (owner-release
  proof preserved across retry).
- Restored the SDK package-smoke TUI-pack fix after CI proved the smoke
  regresses without it.

Supersession audit vs origin/dev (dropped from the original PR):
- workflow-gate-broker.ts + test: Bun Windows fsync EPERM now via #2309.
- .github/workflows/dev-ci.yml + dev-ci-notification-atomicity CI hunk:
  CI topology churn coupled to the dropped workflow.
- sdk-public-surface manifest + CHANGELOG entries: unrelated churn.
- #2292 is already in this fix's base (built upon); #2305 (Discord)
  touches no file in this set.

Repro (#2246): sandboxed operator-safe harness (owned pid only) shows the
detached fixture broker alive + root present before teardown and reaped +
root absent after fixtureRootCleanup.
@Yeachan-Heo
Yeachan-Heo force-pushed the fix/issue-2246-broker-orphan-teardown branch from 216a8c6 to 3690da4 Compare July 15, 2026 22:43

@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: 3690da430e

ℹ️ 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 +100 to +102
"TEMP",
"TMP",
"TMPDIR",

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 Pin fixture temp dirs under the owned root

When fixture children run code that uses os.tmpdir() (for example task/worktree temporary files or SDK snapshot spills), these allowlisted runner TEMP/TMP/TMPDIR values keep temp writes under the real runner temp directory instead of the fixture root that cleanupFixtureRoot removes. That defeats the exact-owner cleanup contract and can leave files outside the preserved/removed root; set the temp variables to a root-owned temp directory rather than forwarding the host values.

Useful? React with 👍 / 👎.

Reconstructs the minimal still-needed exact-owner SDK broker fixture
teardown fix onto current origin/dev, resuming PR #2265. The detached
SDK broker spawned for a fixture is now reaped by targeting the exact
owned child (pid + incarnation), with a bounded SIGTERM -> SIGKILL ->
verified-exit reap and a single-use fixture lease, so a fixture never
orphans its broker owner or leaks its root.

Core:
- src/sdk/broker/ensure.ts: exact-owner reap (reapSpawnedBroker),
  pid+incarnation owner fencing, single fixture lease.
- src/sdk/broker/discovery.ts: broker process incarnation; temp
  broker.json fsync is fail-closed (syncFile) while only the directory
  barrier (syncDirectory) tolerates win32 EPERM/EACCES.
- src/sdk/bus/index.ts: retained terminal-cleanup proof; cleanupRetries
  fences same-id startup so a replacement never shadows an unreaped owner.
- src/sdk/bus/native-runtime-compatibility.ts + crates (gjc-sdk server,
  pi-natives): bounded stop_and_wait/stopAndWait joining half-open and
  established connections idempotently.
- ai/model-cache.ts: close the shared cache only when it owns the exact
  db path. bridge-client: idempotent close() that always issues
  socket.close() under a dedicated close grace (never gated on an expired
  request deadline), so a transport is never leaked on close.
- test/helpers/fixture-broker-cleanup.ts (new): root-wide phased fixture
  teardown wired into sdk-production-host and sdk-lifecycle-fixture; child
  temp dirs (TMPDIR/TMP/TEMP) are pinned inside the owned root so
  os.tmpdir() writes never escape or survive fixture cleanup.
- scripts/build-sdk-package-smoke.ts: pack the matching local @gajae-code/tui
  so the SDK package smoke exercises the workspace release boundary instead
  of an older registry tui missing the SecretInput export (fixes
  bridge-client-sdk-package-smoke; pre-existing on dev).

Exact-head hostile-review repairs:
- bridge-client close() no longer throws on an elapsed operation deadline
  before socket.close(); dedicated close grace + leak regression test.
- Regression tests assert temp broker.json fsync stays fail-closed (even
  for EPERM) and the directory-barrier tolerance is win32-scoped only.
- sdk-production-host suppresses the session's auto-added SDK host during
  construction (GJC_SDK_DISABLE=1 scoped to createAgentSession) so ONLY the
  fixture's explicit instrumented notifications extension hosts; regression
  asserts exactly one server + one endpoint (no duplicate auto-host).
- Fixture temp vars pinned to a root-owned dir with a no-external-residue
  regression.
- Confirmed cleanupRetries already fences same-id startup; restored the SDK
  package-smoke TUI-pack + deterministic sdk-public-surface manifest.

Supersession audit vs origin/dev (dropped from the original PR):
- workflow-gate-broker.ts + test: Bun Windows fsync EPERM now via #2309.
- .github/workflows/dev-ci.yml + dev-ci-notification-atomicity CI hunk:
  CI topology churn coupled to the dropped workflow.
- CHANGELOG entries: unrelated churn.
- #2292 is already in this fix's base (built upon); #2305 (Discord)
  touches no file in this set.

Repro (#2246): sandboxed operator-safe harness (owned pid only) shows the
detached fixture broker alive + root present before teardown and reaped +
root absent after fixtureRootCleanup.
@Yeachan-Heo
Yeachan-Heo force-pushed the fix/issue-2246-broker-orphan-teardown branch from 3690da4 to 80682e8 Compare July 15, 2026 23:17

@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: 80682e8338

ℹ️ 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".

// endpoint and onSdkRequest instrumentation is never overwritten.
const priorSdkDisable = process.env.GJC_SDK_DISABLE;
process.env.GJC_SDK_DISABLE = "1";
let session: Awaited<ReturnType<typeof createAgentSession>>["session"];

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 Replace the new ReturnType-based session type

The repo-level AGENTS.md Code quality section says, "Never use ReturnType<>; write the actual type name." This newly added helper type uses ReturnType<typeof createAgentSession> for the session object, so it violates the documented contributor contract and spreads a banned type idiom; please use the explicit session type instead.

Useful? React with 👍 / 👎.

@Yeachan-Heo
Yeachan-Heo merged commit 0a5098c into dev Jul 15, 2026
44 checks passed
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.

1 participant