Skip to content

[3376 by Claude Opus 5.5] Intermittent desktop packaged-acceptance failure (Linux x64), seen twice in 24h: PR #3368 run 40798090873 (passed on rerun) and nightly run 40837471417 job 'Nightly desktop package / Validate unsigned linux-x64 package'. Check current upstream main first. - #3378

Merged
integry merged 3 commits into
mainfrom
3376/claude-opus-5-5-intermittent-desktop-pack-20261029-1103-efe
Oct 29, 2026

Conversation

@propr-dev

@propr-dev propr-dev Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

AI Implementation Summary

Closes #3376

Branch: 3376/claude-opus-5-5-intermittent-desktop-pack-20261029-1103-efe
Commits: ✅ Changes committed (bb9fe12)


AI Processing Completed

Execution Details:

Summary:
The flake is option (b), a gap in the classifier. The product isn't racing, and I fixed it in the acceptance harness without loosening the strict gate. The new tests pass, but I couldn't run npm run desktop:acceptance here, so it isn't yet proven against a real packaged run.

Upstream main: I couldn't fetch, because the worktree's git directory is read-only. The local origin/main ref is the same commit as this branch (700ce8273), and nothing there already covers this.

Why it's (b):

  • Intended behaviour: after activation, main switches the CSP and schedules a renderer reload. Until the new document loads, the old document's requests to the new endpoint are blocked on purpose (see the comment at apps/desktop/src/main.ts:1993). The new document then does the real current-user sync. The classifier already has an expected category for exactly this denial during csp-reload-pairing.
  • Why it flaked: the classifier only exempts a console CSP error if Chromium's CDP Audits.issueAdded event confirms a matching blocked fetch. That event arrives asynchronously, and on the failing runs the reload replaced the old document first. So the console error had no matching request, and the classifier correctly refused it. That fits the failing run's requestFailures: [] and the single "Connecting to … violates" line.
  • Why I didn't change the product: reloading synchronously wouldn't help, because the old document keeps running until the new one loads. Fencing the renderer's requests would mean changing propr-ui, and this task is desktop-only.

The change is in apps/desktop/scripts/packaged-acceptance-csp-reload.mjs:

  • New evidence record: when the old main-frame document is replaced by a new main-frame document in the same phase, the CDP observer records a document-replaced-before-audit entry. It does this only for fetches that were seen exactly once and never got an Audits issue. Closing the app, subframe documents and repeated fetches produce nothing.
  • Narrow acceptance: the classifier accepts that record only for the current-user csp-connect-policy console error, and only when there's no Audits-confirmed denial. Everything else is still required: exact URL, page and phase, the converged reload evidence, and the existing count limits. Demo-mode, "Fetch API cannot load" errors, other phases and unrelated errors still fail.
  • Diagnostics: the new record appears in the diagnostics as failureClass: 'document-replaced'. No retries were added, and the unexpected-error check is unchanged.

Regression tests: I added three tests to packaged-acceptance-csp-reload.test.mjs. They use the fake CDP session and play events in the failing order: fetch, then document replacement, then no Audits issue. The failing nightly shape now counts as one expected CSP transition with zero unexpected errors, and an extra unrelated error still fails. A late Audits issue still works, and there are negative cases for each condition above. All three fail on the original code.

Checks:

  • Desktop unit tests (npm run desktop:test): 1355 passed, 0 failed, 56 skipped.
  • eslint --max-warnings 0 apps/desktop/: clean.
  • npm run desktop:acceptance failed straight away. There's no packaged out/propr-desktop-linux-x64 build, and the container has no Xvfb or keyring to run it. The real-Chromium test in that file is also skipped here, because it only runs in the provisioned CI job. A Linux x64 packaged-acceptance run in CI will be the real confirmation.

This isn't a visual change, so there are no previews. I also updated my notes on this flake with the fix.

Detailed Logs:

  • Conversation: 107 messages
  • Session: 5a7adb7a-c568-45fb-9051-85b8e2478f0a

Log files stored at:

  • conversation: /usr/src/app/logs/executions/issue-3376-2026-10-29T11-13-25-335Z-conversation.json
  • output: /usr/src/app/logs/executions/issue-3376-2026-10-29T11-13-25-335Z-output.txt
Latest Conversation Messages
ASSISTANT: [content unavailable]

ASSISTANT: The flake is option (b), a gap in the classifier. The product isn't racing, and I fixed it in the acceptance harness without loosening the strict gate. The new tests pass, but I couldn't run `npm run ...


This PR was created automatically by ProPR after processing issue #3376.


💡 Need changes?

Comment on this PR to request refinements — the AI agent monitors comments and will update the implementation based on your feedback. Keep iterating until you're satisfied!

…acing, and I fixed it in the acceptance harness without loosening the strict gate. The new tests pass, but I couldn't run `npm run desktop:acceptance` here, so it isn't yet proven against a real packaged run.

Upstream main: I couldn't fetch, because the worktree's git directory is read-only. The local `origin/main` ref is the same commit as this branch (`700ce8273`), and nothing there already covers this.

Why it's (b):
- Intended behaviour: after activation, main switches the CSP and schedules a renderer reload. Until the new document loads, the old document's requests to the new endpoint are blocked on purpose (see the comment at `apps/desktop/src/main.ts:1993`). The new document then does the real current-user sync. The classifier already has an expected category for exactly this denial during `csp-reload-pairing`.
- Why it flaked: the classifier only exempts a console CSP error if Chromium's CDP `Audits.issueAdded` event confirms a matching blocked fetch. That event arrives asynchronously, and on the failing runs the reload replaced the old document first. So the console error had no matching request, and the classifier correctly refused it. That fits the failing run's `requestFailures: []` and the single "Connecting to … violates" line.
- Why I didn't change the product: reloading synchronously wouldn't help, because the old document keeps running until the new one loads. Fencing the renderer's requests would mean changing `propr-ui`, and this task is desktop-only.

The change is in `apps/desktop/scripts/packaged-acceptance-csp-reload.mjs`:
- New evidence record: when the old main-frame document is replaced by a new main-frame document in the same phase, the CDP observer records a `document-replaced-before-audit` entry. It does this only for fetches that were seen exactly once and never got an Audits issue. Closing the app, subframe documents and repeated fetches produce nothing.
- Narrow acceptance: the classifier accepts that record only for the current-user `csp-connect-policy` console error, and only when there's no Audits-confirmed denial. Everything else is still required: exact URL, page and phase, the converged reload evidence, and the existing count limits. Demo-mode, "Fetch API cannot load" errors, other phases and unrelated errors still fail.
- Diagnostics: the new record appears in the diagnostics as `failureClass: 'document-replaced'`. No retries were added, and the unexpected-error check is unchanged.

Regression tests: I added three tests to `packaged-acceptance-csp-reload.test.mjs`. They use the fake CDP session and play events in the failing order: fetch, then document replacement, then no Audits issue. The failing nightly shape now counts as one expected CSP transition with zero unexpected errors, and an extra unrelated error still fails. A late Audits issue still works, and there are negative cases for each condition above. All three fail on the original code.

Checks:
- Desktop unit tests (`npm run desktop:test`): 1355 passed, 0 failed, 56 skipped.
- `eslint --max-warnings 0 apps/desktop/`: clean.
- `npm run desktop:acceptance` failed straight away. There's no packaged `out/propr-desktop-linux-x64` build, and the container has no Xvfb or keyring to run it. The real-Chromium test in that file is also skipped here, because it only runs in the provisioned CI job. A Linux x64 packaged-acceptance run in CI will be the real confirmation.

This isn't a visual change, so there are no previews. I also updated my notes on this flake with the fix.
@propr-dev

propr-dev Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

CI failed: Prove stock Chromium existing-target axe boundary

Please investigate and fix this CI failure.

  • Check: Prove stock Chromium existing-target axe boundary
  • Result: failure
  • Commit: bb9fe1255cdf (bb9fe1255cdf39948505124c614d4ed7191db9eb)
  • Details: View CI failure

Failure evidence

.github:341
Process completed with exit code 1.

@propr-dev

propr-dev Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

CI failed: Finalize unsigned validation checksums

Please investigate and fix this CI failure.

  • Check: Finalize unsigned validation checksums
  • Result: failure
  • Commit: bb9fe1255cdf (bb9fe1255cdf39948505124c614d4ed7191db9eb)
  • Details: View CI failure

Failure evidence

.github:42
Process completed with exit code 1.

.github:41
Unsigned packaging matrix finished with result 'skipped'.

.github:1
"The ubuntu-latest label will migrate to Ubuntu 26 beginning October 19, 2026. For more information, see actions/runner-images#14748"

@propr-dev

propr-dev Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

✅ Applied the requested follow-up changes in commit 78229cd

Processed 2 comments:

Summary of Changes

I fixed both CI failures; they had one cause. The real-Chromium test in the axe-boundary job showed a soundness bug in the previous commit. Locally, against the same Chromium as CI (151.0.7922.34), that test now passes.

Why the job failed: the failing test was exact observer preserves native denied evidence across real Chromium navigation, with 6 !== 4 at apps/desktop/scripts/packaged-acceptance-csp-reload.test.mjs:607. The previous commit recorded "document replaced before its CSP report arrived" for every single fetch in the old page that had no denial report. That included fetches CSP had allowed and that got a normal response. So navigating from the allowed page back to the denied page added two false entries. In practice, that could have excused a console CSP error using a request that was never blocked.

Why "Finalize unsigned validation checksums" failed: this was only a knock-on effect. The packaging matrix depends on the axe-boundary job, so it was skipped, and this job requires the matrix to succeed. Nothing in it needed changing.

The fix (in packaged-acceptance-csp-reload.mjs):

  • The fetch wrapper the harness injects into the page now also logs when a watched fetch gets a response. The caller still gets the original promise, rejections included.
  • The observer marks those fetches as allowed, even if the test phase changed before the response came back. Allowed fetches never produce a "document replaced" entry.
  • The narrow acceptance rule from the previous commit is unchanged, so a genuinely blocked current-user fetch still counts.

New tests:

  • An allowed fetch followed by a page replacement produces nothing, whether the response arrives in the same phase or a later one.
  • The wrapper logs the response only for watched URLs, and leaves rejected fetches unreported and still rejected.

Checks:

  • The real-Chromium test (PROPR_DESKTOP_REAL_CHROMIUM_BOUNDARY=1, the CI command): 51 passed, 0 failed.
  • The same test against the previous commit fails here with 6 !== 4, so the CI failure reproduces locally.
  • Desktop unit tests (npm run desktop:test): 1355 passed, 0 failed, 56 skipped.
  • eslint --max-warnings 0 on the two changed files: clean.

As before, I couldn't run the full packaged acceptance (npm run desktop:acceptance) here, so a CI run is still needed to confirm the original flake fix on a real Linux build. No previews, since nothing visible changed.


🤖 Implementation Details

  • Model: Claude Opus 5.5 (medium)
  • Requested By: @propr-dev[bot]
  • Time: 5m 56s
  • Tokens: 1,168,433 (1,159,624 in / 8,809 out)
  • Cost: $0.68

Undo Changes • View Task Execution


💡 ProPR Slash Commands
Command Description Example
/merge Merge target branch into this PR and resolve conflicts /merge
/review Request an AI code review /review or /review claude-sonnet
/fix Address review blockers, or blockers and suggestions with /fix all /fix, /fix all, or /fix F3 S5
/switch Change the AI model for this PR /switch claude-opus
/use Override the model for a single follow-up run /use claude-sonnet
/ultrafix Loop review→fix cycles until score goal is met /ultrafix or /ultrafix goal=8 max=10

🤖 Generated by ProPR
Processing comment IDs: 6495148508✓, 6495149624✓

…mium test in the axe-boundary job showed a soundness bug in the previous commit. Locally, against the same Chromium as CI (151.0.7922.34), that test now passes.

I fixed both CI failures; they had one cause. The real-Chromium test in the axe-boundary job showed a soundness bug in the previous commit. Locally, against the same Chromium as CI (151.0.7922.34), that test now passes.

**Why the job failed:** the failing test was `exact observer preserves native denied evidence across real Chromium navigation`, with `6 !== 4` at `apps/desktop/scripts/packaged-acceptance-csp-reload.test.mjs:607`. The previous commit recorded "document replaced before its CSP report arrived" for every single fetch in the old page that had no denial report. That included fetches CSP had allowed and that got a normal response. So navigating from the allowed page back to the denied page added two false entries. In practice, that could have excused a console CSP error using a request that was never blocked.

**Why "Finalize unsigned validation checksums" failed:** this was only a knock-on effect. The packaging matrix depends on the axe-boundary job, so it was skipped, and this job requires the matrix to succeed. Nothing in it needed changing.

**The fix** (in `packaged-acceptance-csp-reload.mjs`):
- The fetch wrapper the harness injects into the page now also logs when a watched fetch gets a response. The caller still gets the original promise, rejections included.
- The observer marks those fetches as allowed, even if the test phase changed before the response came back. Allowed fetches never produce a "document replaced" entry.
- The narrow acceptance rule from the previous commit is unchanged, so a genuinely blocked current-user fetch still counts.

**New tests:**
- An allowed fetch followed by a page replacement produces nothing, whether the response arrives in the same phase or a later one.
- The wrapper logs the response only for watched URLs, and leaves rejected fetches unreported and still rejected.

**Checks:**
- The real-Chromium test (`PROPR_DESKTOP_REAL_CHROMIUM_BOUNDARY=1`, the CI command): 51 passed, 0 failed.
- The same test against the previous commit fails here with `6 !== 4`, so the CI failure reproduces locally.
- Desktop unit tests (`npm run desktop:test`): 1355 passed, 0 failed, 56 skipped.
- `eslint --max-warnings 0` on the two changed files: clean.

As before, I couldn't run the full packaged acceptance (`npm run desktop:acceptance`) here, so a CI run is still needed to confirm the original flake fix on a real Linux build. No previews, since nothing visible changed.

PR: #3378
Comment by: @propr-dev[bot] (ID: 6495148508)
Comment by: @propr-dev[bot] (ID: 6495149624)
Model: Claude Opus 5.5
@propr-dev

propr-dev Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

CI failed: Validate unsigned linux-x64 package

Please investigate and fix this CI failure.

  • Check: Validate unsigned linux-x64 package
  • Result: failure
  • Commit: 78229cdb9309 (78229cdb9309b0ea2e9edc2adf8899c877d1e623)
  • Details: View CI failure

Failure evidence

.github:56
Process completed with exit code 1.

@propr-dev

propr-dev Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

✅ Applied the requested follow-up changes in commit 172a5eb

Summary of Changes

I made a likely fix for this failure, but I can't prove it: the CI log doesn't say why the step failed, and I couldn't run the packaged lifecycle here. The failure isn't in the code this PR changed, and the real check is the next CI run.

What failed: the earlier step, the packaged acceptance run this PR fixes, passed in this run on linux-x64. The step that failed was the later native install/deep-link lifecycle step, with Native lifecycle operation failed [stage:PROTOCOL_DISPATCH] and no other detail. That's by design: the script hides child output, URLs and profile paths, so the cause has to be worked out from the code.

Why I think it's the ZIP:

  • If a registration command for the deb or rpm package had failed, the error would name a more specific stage (PROTOCOL_LAUNCH and similar). A bare PROTOCOL_DISPATCH means the error came from launching a second app instance directly, which is how the ZIP has to be dispatched. It could also come from writing the package's launcher file, but that's less likely.
  • On Linux the packages run in the order deb, rpm, zip, and the step failed about 55 seconds in, which fits the ZIP being last.

The likely cause: that ZIP second-instance launch was the only warm dispatch started without --disable-gpu --password-store=gnome-libsecret. Every other dispatch passes them, and so does the rewritten launcher line used for the deb and rpm. They were added everywhere in commit 45e8ce344 except here. Missing them under the CI's virtual display could plausibly make that instance miss its 15-second exit deadline now and then. That would fit this signature appearing once, only on x64: linux-arm64 passed the same step on the same commit, and none of the 10 earlier failed desktop runs I checked had it.

Changes:

  • apps/desktop/scripts/test-native-artifact-lifecycle.mjs: the protocol-dispatch helper now takes the launch arguments and passes them to the ZIP launch, and the lifecycle passes them in. I also gave the helper an injectable dispatch function so the ZIP path can be tested.
  • apps/desktop/scripts/test-native-artifact-lifecycle.test.mjs: a new case checks that the ZIP launch gets both arguments and never registers an OS launcher. With the old call restored, it fails as expected.

Checks: the lifecycle suite (44/44), linux-preview-workflow.test.mjs (17/17) and release-workflow.test.ts (29/29) pass, and eslint shows no warnings on both files.

If PROTOCOL_DISPATCH comes back even with the arguments passed, treat it as a runner flake and rerun. There's nothing visual to preview.


🤖 Implementation Details

  • Model: Claude Opus 5.5 (medium)
  • Requested By: @propr-dev[bot]
  • Time: 6m 55s
  • Tokens: 1,766,727 (1,756,154 in / 10,573 out)
  • Cost: $0.87

Undo Changes • View Task Execution


💡 ProPR Slash Commands
Command Description Example
/merge Merge target branch into this PR and resolve conflicts /merge
/review Request an AI code review /review or /review claude-sonnet
/fix Address review blockers, or blockers and suggestions with /fix all /fix, /fix all, or /fix F3 S5
/switch Change the AI model for this PR /switch claude-opus
/use Override the model for a single follow-up run /use claude-sonnet
/ultrafix Loop review→fix cycles until score goal is met /ultrafix or /ultrafix goal=8 max=10

🤖 Generated by ProPR
Processing comment ID: 6495494400✓

… the CI log doesn't say why the step failed, and I couldn't run the packaged lifecycle here. The failure isn't in the code this PR changed, and the real check is the next CI run.

I made a likely fix for this failure, but I can't prove it: the CI log doesn't say why the step failed, and I couldn't run the packaged lifecycle here. The failure isn't in the code this PR changed, and the real check is the next CI run.

**What failed:** the earlier step, the packaged acceptance run this PR fixes, passed in this run on linux-x64. The step that failed was the later native install/deep-link lifecycle step, with `Native lifecycle operation failed [stage:PROTOCOL_DISPATCH]` and no other detail. That's by design: the script hides child output, URLs and profile paths, so the cause has to be worked out from the code.

**Why I think it's the ZIP:**
- If a registration command for the deb or rpm package had failed, the error would name a more specific stage (`PROTOCOL_LAUNCH` and similar). A bare `PROTOCOL_DISPATCH` means the error came from launching a second app instance directly, which is how the ZIP has to be dispatched. It could also come from writing the package's launcher file, but that's less likely.
- On Linux the packages run in the order deb, rpm, zip, and the step failed about 55 seconds in, which fits the ZIP being last.

**The likely cause:** that ZIP second-instance launch was the only warm dispatch started without `--disable-gpu --password-store=gnome-libsecret`. Every other dispatch passes them, and so does the rewritten launcher line used for the deb and rpm. They were added everywhere in commit `45e8ce344` except here. Missing them under the CI's virtual display could plausibly make that instance miss its 15-second exit deadline now and then. That would fit this signature appearing once, only on x64: linux-arm64 passed the same step on the same commit, and none of the 10 earlier failed desktop runs I checked had it.

**Changes:**
- `apps/desktop/scripts/test-native-artifact-lifecycle.mjs`: the protocol-dispatch helper now takes the launch arguments and passes them to the ZIP launch, and the lifecycle passes them in. I also gave the helper an injectable dispatch function so the ZIP path can be tested.
- `apps/desktop/scripts/test-native-artifact-lifecycle.test.mjs`: a new case checks that the ZIP launch gets both arguments and never registers an OS launcher. With the old call restored, it fails as expected.

**Checks:** the lifecycle suite (44/44), `linux-preview-workflow.test.mjs` (17/17) and `release-workflow.test.ts` (29/29) pass, and eslint shows no warnings on both files.

If `PROTOCOL_DISPATCH` comes back even with the arguments passed, treat it as a runner flake and rerun. There's nothing visual to preview.

PR: #3378
Comment by: @propr-dev[bot] (ID: 6495494400)
Model: Claude Opus 5.5
@integry

integry commented Oct 29, 2026

Copy link
Copy Markdown
Owner

/review

1 similar comment
@integry

integry commented Oct 29, 2026

Copy link
Copy Markdown
Owner

/review

@propr-dev

propr-dev Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

✅ AI Code Review Complete requested by @integry

Posted 1 review:

View Task Details

@propr-dev

propr-dev Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

🔍 AI Code Review — codex:gpt-6-astra

Overall Evaluation

Ready to merge within the supplied scope. The PR adds a narrowly constrained fallback for missing CSP audit evidence and preserves Linux launch arguments during ZIP protocol dispatch. No merge-blocking defect was identified by static review.

✅ Bounded classification — Replacement evidence applies only to the matching current-user connect-policy diagnostic, with page, phase, URL, uniqueness, and convergence checks.

✅ Negative coverage — Added tests cover fulfilled and duplicate fetches, cross-phase replacement, subframes, application closure, and unrelated errors.

✅ Launch consistency — Linux ZIP dispatch now receives the launch context arguments, with a focused forwarding assertion.

The authoritative current-head status reports 29 passed checks and no failures or pending checks, including Linux x64 package validation. No commands were run. The supplied issue body is truncated, so objective validation is limited to its visible requirements.

Merge blockers

No merge blockers.

Suggestions

These are optional follow-ups and are not sent to /fix.

S1: 🟢 Clarify the observer contract

Update the introductory comments in apps/desktop/scripts/packaged-acceptance-csp-reload.mjs to describe the replacement-evidence fallback alongside native audit correlation. They currently describe requiring an actual CSP request failure, while the new implementation also accepts narrowly constrained document-replacement evidence. This is documentation cleanup, not a correctness blocker.

Score

The changed behavior is tightly scoped, has substantial negative coverage, and passes the reported current-head checks. Static review found no required corrections.

Score: 9/10


🤖 Review Details

  • Model: GPT-6 Astra
  • Time: 42s
  • Tokens: 33,295 (32,736 in / 559 out)
  • Cost: $0.25

View Task


💡 Next step: Comment /fix to address every F# merge blocker, /fix all for every pending blocker and suggestion, or name records explicitly, as in /fix F3 S5.
F# and S# IDs increment across review comments and remain permanent, so selectors such as /fix F3 F5 stay unambiguous across cycles. S# suggestions stay optional: they are implemented only when you name them or request /fix all, and they never relax a merge blocker.

Copy and edit to choose records from this review:

/fix S1

🤖 Review by ProPR

@integry
integry merged commit 9e01e2d into main Oct 29, 2026
48 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant