Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions .github/workflows/nix-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,20 @@ concurrency:
# Keying on the event as well keeps a merge train collapsing to its latest push
# without letting it cancel the schedule.
group: nix-build-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
# false, where a PR-branch check would want true. This job takes half an hour
# and main takes merges minutes apart, so cancel-in-progress meant each merge
# evicted the previous run before it finished: the first four runs on main were
# cancelled at 21m38s, 25s, 7m05s and then one that only survived because the
# merges happened to stop. A cancelled run is not a failure either, so the
# branch showed nothing while the workflow reported nothing.
#
# What this buys is that a started run finishes, not that every merge is
# verified: GitHub holds a single pending entry per group and a newer push
# replaces it, so merges landing while a run is in flight still go unbuilt.
# That is the affordable half. Verifying each merge would need a queue this
# workflow does not have, and is not worth it for a half-hour job whose purpose
# is catching drift rather than gating a commit.
cancel-in-progress: false
Comment thread
coderabbitai[bot] marked this conversation as resolved.

jobs:
build:
Expand Down Expand Up @@ -294,6 +307,13 @@ jobs:
# capture failures are a separate problem, already reported above.
echo "--- record then export ---"
EXPORTED=""
# Tracked apart from EXPORTED so the verdict can name the stage that
# actually failed. Every run so far has died in record without export
# ever executing, while the annotation said "the export path does not
# work" -- an accusation aimed at the one component the run never
# reached, and the compositor addon is precisely what this step exists
# to vouch for.
RECORDED=0
for i in 1 2 3; do
echo "=== export attempt $i/3 ==="
rm -f /tmp/demo.openscreen /tmp/demo.mp4
Expand All @@ -302,8 +322,12 @@ jobs:
if [ "$RC" -ne 0 ] || [ ! -f /tmp/demo.openscreen ]; then
echo "record failed (rc=$RC); last lines:"
tail -5 "/tmp/rec.$i.out" || true
# The bound inside get-sources names its own failure; surface it
# rather than leaving the reason five lines up in a scratch file.
grep -a "get-sources\]" "/tmp/rec.$i.out" | tail -3 || true
continue
fi
RECORDED=1
echo "recorded. project:"
head -c 200 /tmp/demo.openscreen; echo

Expand All @@ -319,8 +343,10 @@ jobs:
done

EXPORT_OK=0
if [ -z "$EXPORTED" ]; then
echo "::error::No attempt produced an MP4. The compositor addon is packaged but the export path does not work."
if [ -z "$EXPORTED" ] && [ "$RECORDED" -eq 0 ]; then
echo "::error::No attempt got past record, so export never ran and the compositor addon is unproven. This is a capture failure on this host, not an export failure."
elif [ -z "$EXPORTED" ]; then
echo "::error::record produced a project but no attempt produced an MP4. The compositor addon is packaged and the export path does not work."
else
SIZE=$(wc -c < "$EXPORTED")
# An MP4 opens with a 4-byte length then 'ftyp'. A zero-length or
Expand Down Expand Up @@ -350,7 +376,7 @@ jobs:
# per-attempt warnings above keep that flakiness visible without letting
# it decide the build; tighten this to $ATTEMPTS once the capture failure
# is understood and fixed.
echo "=== verdict: enumeration $OK/$ATTEMPTS ok, export $EXPORT_OK ==="
echo "=== verdict: enumeration $OK/$ATTEMPTS ok, record $RECORDED, export $EXPORT_OK ==="
if [ "$EXPORT_OK" -ne 1 ] || [ "$OK" -eq 0 ]; then
exit 1
fi
42 changes: 35 additions & 7 deletions electron/ipc/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import {
import type { CursorTelemetryReader } from "../ai-edition/deep-agent/service";
import { DocumentService } from "../ai-edition/document-service";
import { LlmConfigStore } from "../ai-edition/llm-config-store";
import { mainLogBuffer } from "../diagnostics/main-log-buffer";
import { isDiagnosticModeEnabled, mainLogBuffer } from "../diagnostics/main-log-buffer";
import { mainT } from "../i18n";
import { getInstallChannel } from "../install-channel";
import { RECORDINGS_DIR } from "../main";
Expand Down Expand Up @@ -1716,12 +1716,40 @@ export function registerIpcHandlers(
// await it, and a renderer-side race would only stop *waiting* while this
// keeps running and its reply goes to nobody. Rejecting is what turns an
// indefinite spinner into the pickers' existing error branch.
const sources = await withDeadline(
desktopCapturer.getSources(opts),
GET_SOURCES_TIMEOUT_MS,
`Desktop source enumeration did not return within ${GET_SOURCES_TIMEOUT_MS}ms. ` +
"This usually means the display or GPU stack cannot be reached — check that a display server is available.",
);
// How long it actually took, under the existing diagnostic flag. The bound
// above turned an indefinite hang into a named failure, which is where the
// open question starts rather than ends: on a headless runner `openscreen
// sources` gets an answer within 20s four times in five while `record` --
// the same call with the same options -- exceeds 30s every time. A duration
// on both paths is what tells those apart; a threshold alone cannot.
const startedAt = Date.now();
const diagnostic = isDiagnosticModeEnabled();
let sources: Awaited<ReturnType<typeof desktopCapturer.getSources>>;
try {
sources = await withDeadline(
desktopCapturer.getSources(opts),
GET_SOURCES_TIMEOUT_MS,
`Desktop source enumeration did not return within ${GET_SOURCES_TIMEOUT_MS}ms. ` +
"This usually means the display or GPU stack cannot be reached — check that a display server is available.",
);
} catch (error) {
if (diagnostic) {
// The reason, not an assumption about it: this catch also sees a
// getSources that rejected on its own, well inside the deadline, and
// calling that a timeout would point the next reader at the wrong thing.
// The deadline error carries its own wording.
const reason = error instanceof Error ? error.message : String(error);
console.info(
`[get-sources] failed after ${Date.now() - startedAt}ms (types=${(opts?.types ?? []).join(",")}): ${reason}`,
);
}
throw error;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
if (diagnostic) {
console.info(
`[get-sources] returned ${sources.length} source(s) in ${Date.now() - startedAt}ms (types=${(opts?.types ?? []).join(",")})`,
);
}
lastEnumeratedSources = new Map(sources.map((source) => [source.id, source]));
return sources.map((source) => ({
id: source.id,
Expand Down
Loading