Skip to content

Commit d80a0bd

Browse files
committed
🤖 fix: handle bash backgrounding + idle bump fallback
Change-Id: I9c70f712251657b4a57c71fc9f3cab9f2a05cc4e Signed-off-by: Thomas Kosiewski <tk@coder.com>
1 parent 1ecb249 commit d80a0bd

File tree

3 files changed

+26
-8
lines changed

3 files changed

+26
-8
lines changed

docs/AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ gh pr view <number> --json mergeable,mergeStateStatus | jq '.'
6767
## Refactoring & Runtime Etiquette
6868

6969
- Use `git mv` to retain history when moving files.
70-
- Never kill the running mux process; rely on `make test` / `make typecheck` for validation.
70+
- Never kill the running mux process; rely on `make typecheck` + targeted `bun test path/to/file.test.ts` for validation (run `make test` only when necessary; it can be slow).
7171

7272
## Testing Doctrine
7373

src/browser/stores/WorkspaceStore.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,18 @@ export class WorkspaceStore {
535535
return;
536536
}
537537

538+
// requestIdleCallback is not available in some environments (e.g. Node-based unit tests).
539+
// Fall back to a regular timeout so we still throttle bumps.
540+
if (typeof requestIdleCallback !== "function") {
541+
const handle = setTimeout(() => {
542+
this.deltaIdleHandles.delete(workspaceId);
543+
this.states.bump(workspaceId);
544+
}, 0);
545+
546+
this.deltaIdleHandles.set(workspaceId, handle as unknown as number);
547+
return;
548+
}
549+
538550
const handle = requestIdleCallback(
539551
() => {
540552
this.deltaIdleHandles.delete(workspaceId);
@@ -591,7 +603,11 @@ export class WorkspaceStore {
591603
private cancelPendingIdleBump(workspaceId: string): void {
592604
const handle = this.deltaIdleHandles.get(workspaceId);
593605
if (handle) {
594-
cancelIdleCallback(handle);
606+
if (typeof cancelIdleCallback === "function") {
607+
cancelIdleCallback(handle);
608+
} else {
609+
clearTimeout(handle as unknown as number);
610+
}
595611
this.deltaIdleHandles.delete(workspaceId);
596612
}
597613
}
@@ -1172,11 +1188,7 @@ export class WorkspaceStore {
11721188
this.consumerManager.removeWorkspace(workspaceId);
11731189

11741190
// Clean up idle callback to prevent stale callbacks
1175-
const handle = this.deltaIdleHandles.get(workspaceId);
1176-
if (handle) {
1177-
cancelIdleCallback(handle);
1178-
this.deltaIdleHandles.delete(workspaceId);
1179-
}
1191+
this.cancelPendingIdleBump(workspaceId);
11801192

11811193
const statsUnsubscribe = this.statsUnsubscribers.get(workspaceId);
11821194
if (statsUnsubscribe) {

src/node/services/tools/bash.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,10 +559,12 @@ ${script}`;
559559

560560
// Wait for process exit and stream consumption concurrently
561561
// Also race with the background promise to detect early return request
562+
const foregroundCompletion = Promise.all([execStream.exitCode, consumeStdout, consumeStderr]);
563+
562564
let exitCode: number;
563565
try {
564566
const result = await Promise.race([
565-
Promise.all([execStream.exitCode, consumeStdout, consumeStderr]),
567+
foregroundCompletion,
566568
backgroundPromise.then(() => "backgrounded" as const),
567569
]);
568570

@@ -582,6 +584,10 @@ ${script}`;
582584
/* ignore */ return;
583585
});
584586

587+
// Avoid unhandled promise rejections if the cancelled UI readers cause
588+
// the foreground consumption promise to reject after we return.
589+
void foregroundCompletion.catch(() => undefined);
590+
585591
// Detach from abort signal - process should continue running
586592
// even when the stream ends and fires abort
587593
abortDetached = true;

0 commit comments

Comments
 (0)