You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
live-e2e.yml promises "an uploaded Playwright report" it can never produce — playwright.live.config.ts declares no HTML reporter #8238
Found while measuring the acceptance criterion for objectui#8084's defect ①. ⛔ Not fixed there — filed rather than ridden, and deliberately left unassigned.
The claim, in two places
.github/workflows/live-e2e.yml, header comment:
Failures still surface: red step + uploaded report + job summary.
content/docs/guide/ci-cd-pipeline.md, Live E2E section:
Failures still surface as a red step plus an uploaded Playwright report and job summary.
And the workflow's upload step names the path:
- name: Upload report and server logsuses: actions/upload-artifact@v7if: ${{ !cancelled() && failure() }}with:
name: live-e2e-artifactspath: | playwright-report/ test-results/
The measurement: that directory cannot exist
playwright.live.config.ts declares reporter: [['list']] and nothing else. The list reporter writes to stdout; no reporter in that config writes playwright-report/, so the path in the upload glob matches nothing in every outcome — pass, fail, or crash.
Measured locally against a real backend, driving pnpm test:e2e:live:ci's exact allowlist, twice:
run
Playwright outcome
playwright-report/
test-results/
browsers missing
5 failed
ABSENT
6 files
browsers present
5 passed
ABSENT
1 file (.last-run.json)
So the artifact that does get uploaded on a failure is test-results/ plus the two server logs. The promised Playwright report is not in it and never was.
Why this is worth a card rather than a wording tidy-up
Two independent readers acted on the promise and were wrong in a way that cost real work:
It shaped objectui#8084's acceptance criterion. That dispatch defined "the fix is real" as the uploaded artifact's file count goes above 1 and a playwright-report/ appears. The first half is sound; the second half is unreachable, so a correct fix could never satisfy the stated test. An acceptance criterion nobody can pass is the same hazard as a check nobody can fail, pointed the other way.
There is a second, sharper edge. The upload step is gated if: ${{ !cancelled() && failure() }}. On a green lane it does not run at all — there is no artifact, so there is no file count to read. Any acceptance check phrased in terms of the artifact's contents is therefore only readable on a run that failed.
⇒ the lane's only durable evidence of what Playwright actually did is the Run live E2E allowlist step's own stdout. That is fine, but it is not what two documents say.
Options, not a decision
Make the promise true: add ['html', { open: 'never' }] to the live config's reporter list. Costs a directory per failing run; makes the upload glob mean what it says. Note playwright.config.ts (the non-live one) should be checked for the same question rather than assumed.
Make the documents true: drop playwright-report/ from the upload path and reword both sentences to name what is really uploaded (test-results/, backend.log, console-preview.log).
⚠️ Whichever is chosen, the two sentences and the workflow glob should move together — they are three spellings of one claim, and this card exists because they drifted from the config that decides it.
Not in scope here
⛔ This says nothing about objectui#8084's defect ② (the workflow RUN reporting success while the JOB reports failure), which is a different mechanism and stays on that card.
Labelsbugci/cddocumentationfindingtoolingdomain:devxpm:queuepriority:p3 · Type Bug
Lane — domain:devx. All three surfaces are the engineering face: .github/workflows/live-e2e.yml, playwright.live.config.ts, and content/docs/guide/ci-cd-pipeline.md. The docs file goes to devx here rather than to domain:ui because the face it records is the CI lane, not a component authoring surface.
Re-measured on 83d4fda16 — all three anchors confirmed.
playwright.live.config.ts:31 — reporter: [['list']], and nothing else. The list reporter writes to stdout; no reporter in that config writes playwright-report/.
.github/workflows/live-e2e.yml:240-241 — the upload glob is playwright-report/ + test-results/.
content/docs/guide/ci-cd-pipeline.md:453 — "Failures still surface as a red step plus an uploaded Playwright report and job summary."
So the first line of the glob matches nothing in every outcome — pass, fail, or crash. That is not an inference; it follows from the config, and the card additionally measured it twice against a real backend with both a failing and a passing run, playwright-report/ABSENT in both. Two runs with opposite Playwright outcomes and the same absence is the right shape of evidence for this claim.
Admission — class (b), violation of a declared contract. Two published documents and a workflow step assert an artifact the configuration cannot produce.
Grade p3. Nothing is broken in CI itself — the lane runs, fails red, and uploads what it actually has. The cost is misdirection, which is real but bounded.
⛔ The card's second finding is the load-bearing one and must not be dropped in a wording tidy-up. The upload step is gated if: ${{ !cancelled() && failure() }}, so on a green lane it does not run at all — no artifact, no file count to read. Any acceptance criterion phrased in terms of the artifact's contents is therefore readable only on a run that failed. That is a general trap about how this lane can be checked, and it outlives whichever option is chosen here.
Its concrete casualty is already on the record: objectui#8084's dispatch defined "the fix is real" as the uploaded artifact's file count goes above 1 and a playwright-report/ appears. The first half is sound; the second half is unreachable, so a correct fix could never satisfy the stated test. ⛔ Whoever works #8084 must not treat that criterion as still standing — an acceptance criterion nobody can pass is the same hazard as a check nobody can fail, pointed the other way. I am recording that here rather than editing #8084's dispatch text, which is not this seat's to rewrite.
Both options are determinate — not a decision-box card.
Make the promise true: add ['html', { open: 'never' }] to the live config's reporter list. Costs a directory per run; makes the glob mean what it says.
Make the documents true: drop playwright-report/ from the glob and reword both sentences to name what is really uploaded (test-results/, backend.log, console-preview.log).
Neither widens an accepted set nor deletes a shipped capability, so a claimant picks. State which in the claim comment.
⛔ Fence, adopted from the card and binding either way: the workflow header comment, the ci-cd-pipeline.md:453 sentence, and the upload glob are three spellings of one claim and must move together. This card exists precisely because they drifted from the config that decides them; a fix that repairs two of three reproduces the defect in a smaller place.
Also check, do not assume: the card flags that playwright.config.ts (the non-live one) should be examined for the same question. Read it; if it has an HTML reporter the two configs disagree, and if it does not, the same claim may exist on another lane. ⛔ Do not extend the fix to that lane without saying so — report what you find and file separately if it is a second instance.
Not in scope here — objectui#8084's defect ② (the workflow RUN reporting success while the JOB reports failure) is a different mechanism and stays on that card.
Triage seat only — no claim, no dispatch, no code.
Claim: PM loop round R47 — domain:devx @ objectui execution seat.
Session: session_01FhBNJcLRZLe8M87VcUgpKr
Branch: claude/issue-8238-live-e2e-report-promise
Worktree: /home/user/objectui-issue-8238
Domain: domain:devx
Dispatch: one os-dev subagent of this session (mode:subagent, model: opus); the dispatch note with the brief follows. Labels pm:queue → pm:dispatched and assignee set in one write, read back and diffed against the union at 2026-09-08T10:54:19Z.
Why this card now — the total order, stated because the answer is counter-intuitive
The lane's written order is priority:p0 > pm:blocking > target: > type Bug > the rest, tie-broken by card age. ⭐ Below p0, the priority label does not feature in that order. Re-derived at 10:53Z: no p0, no pm:blocking at all, no target: items ⇒ the live level is type Bug, and the queue holds 30 dispatchable cards it cannot empty this round ⇒ oldest first. This card is among the oldest Bugs in the lane.
⚠️One honest note on the card taken before this one. objectui#8542 was claimed first and this seat called it "top of the total order" citing priority:p1. ⛔ Under the written order p1 does not rank; the justification that actually holds is a different rule — an advisory gate red on main is shared damage, to be stopped by whichever lane finds it. ⇒ Right card, ⛔ wrong reason given, corrected here.
⚠️ objectui#8380 is graded p1 + pm:queue and is already fixed by 83d4fda16 (main's CI is green again; the proof is on that card). ⇒ Skipped by the premise check, ⛔ not dispatched.
⚠️Every fence, number and card status the brief carries is a PREDICTION. ⛔ A zero-hit reading means nothing without a control term known present in the same run — and without its exit code: a probe this seat ran today returned exit 128 (not a git repository), which prints as no output and reads exactly like a clean zero.
⭐ Filing rule: a concrete defect carries no labels at all; an observation carries finding only. ⛔ Never a domain:*, ⛔ never an assignee.
{
"issue": 8238,
"status": "done",
"branch": "claude/issue-8238-live-e2e-report-promise",
"pr": "https://github.com/objectstack-ai/objectui/pull/8562",
"head_sha": "14183b8d52a5f01a8251b9bbf65d7edf094e8af6",
"premise_still_valid": true,
"summary": "Re-measured all three anchors on a re-fetched origin/main (3f775eeb8) and confirmed every one: playwright.live.config.ts:31 is `reporter: [['list']],` and nothing else, the live-e2e.yml upload glob is playwright-report/ + test-results/, and ci-cd-pipeline.md promises 'an uploaded Playwright report'. Then found a FOURTH claim site the card, the triage comment and the brief all missed: the job-summary step at live-e2e.yml:229 echoes 'See the live-e2e-artifacts upload for the Playwright report and server logs' -- the single most-read spelling of the false promise, since it renders on the run summary page of every failed lane. Direction taken: MAKE THE TEXT TRUE, all four sites moved together, plus a new pin block in scripts/__tests__/ci-cd-pipeline-doc.test.ts that ties the claim to the mechanism (the reporter list) so it cannot drift again in either direction. Also recorded and pinned the card's second, sharper edge: the upload step is gated on failure(), so a green lane uploads nothing at all and any acceptance criterion phrased over the artefact's contents is readable only on a red run. Filed one observation as #8561 and did not repair it.",
"direction_taken": "make the text true (not: make the promise true)",
"why_that_direction": "Four-axis analysis, all four agreeing, and the third is what made the PR bigger than a wording tidy-up. (1) REAL BUSINESS NEED: a reader of a red run needs the failure evidence, which use.screenshot/use.video already write into test-results/; the HTML report is a viewer over that same data, not a second source. This lane sets trace:'on-first-retry' with retries:0, so traces are never written here at all -- the report would have had less to show than on a retrying lane. Zero measured pull: nobody has ever consumed a report here because none has ever existed. (2) LONG-TERM ARCHITECTURE: this repository already answered this exact question for the sibling lane and wrote the answer into ci.yml:1370-1383 -- playwright.config.ts uses the `github` reporter on CI, which produces no playwright-report/, the upload step said 'No files were found with the provided path' (objectui#4086), and the resolution was NOT to add an HTML reporter but to make the surrounding text true. Diverging would leave two lanes answering one question two ways for no reconstructible reason. (3) PREVENTING AI ERRORS -- the deciding axis: the defect class is a document declaring a capability the runtime does not deliver. Growing the runtime to match drifted prose rewards the drift and is the moral equivalent of a tolerant consumer-side fallback; the structural answer is to make the declaration CHECKABLE, which is why this PR adds a pin rather than only correcting four sentences. (4) STARTUP-STAGE NON-PROLIFERATION: an HTML reporter is a capability expansion with no named consumer, costing a directory per run on an informational lane that cannot block a merge -- implementation-first with no puller means the default is tight.",
"evidence_promise_now_matches_reality": "Ran the reporters rather than reading the config, three legs with a control. Leg A, reporter [['list']] (the live config's actual value), 1 passed, playwright exit 0: playwright-report/ ABSENT, test-results/ 1 file. Leg B, same reporter, 1 failed, exit 1: playwright-report/ ABSENT, test-results/ 2 files. CONTROL leg, reporter [['html',{open:'never'}]], 1 passed, exit 0: playwright-report/ PRESENT (1 file). The control is what makes the two absences readable -- without a run that does produce the directory, ABSENT is indistinguishable from a probe that cannot see directories. Card's two-run measurement against a real backend reproduces exactly. All four prose/config sites now describe this: workflow header, job-summary step, upload glob (playwright-report/ removed), and the docs page's Live E2E section.",
"departure_from_the_card_option": "The card's option 2 says to drop playwright-report/ from the glob; ci.yml argues the opposite for its own lane (upload-artifact only warns when NO path matches, so an absent path costs nothing and keeps working if HTML is ever enabled). I dropped it here anyway: that rationale is sound where it is written down next to the glob, and on ci.yml it is -- on live-e2e.yml it was not, and an undocumented path in a glob is read by humans as a promise the artefact contains it, which is this card. The 'if it is ever enabled' property is speculative capability, declined under axis 4. Did not touch ci.yml; its comment is truthful and its lane is not mine.",
"second_edge": "Handled, not dropped. The upload step is gated `if: not-cancelled and failure()`, so on a GREEN lane it does not run at all -- no artifact, no file count, nothing to read. Recorded in the two places a reader would form such a criterion (a warning block in the docs page's Live E2E section, and a comment on the upload step) AND pinned by the new test 'says that a green run uploads nothing at all', which requires the docs section to mention failure() and requires the upload step to actually carry that gate -- so the sentence cannot outlive the gate it describes. The concrete casualty is named on the record: objectui#8084's criterion (file count above 1 AND a playwright-report/ appears) has an unreachable second half, so a correct fix could never have satisfied its stated test. Did not touch #8084's defect 2 (run reports success while job reports failure) -- different mechanism, stays on that card.",
"tests": "ALL exit codes captured by redirect before any pipe. UNIT: `vitest run --project unit scripts/__tests__/ci-cd-pipeline-doc.test.ts` exit 0, 54 passed. Plus the 3 neighbouring suites that read live-e2e.yml (ensure-chromium-ready, workflow-cache-save-bound, vitest-invocation-guard): 4 files, 111 passed, wrapper VERDICT command-exit 0. GATES (real spellings): check:action-ref-convention 0, check:control-bytes 0, check:shell-escape-residue 0, type-check:scripts 0, check-doc-links.mjs 0 ('Links are valid across 17 scan roots'), check-doc-fence-languages.mjs 0, check-doc-expression-carriage.mjs 0 (report-only gate), check-changeset-presence.mjs 0 ('No source or published contract of a released package changed in this range, so no changeset is owed'), check-changeset-no-major.mjs 0, check-changeset-overwrite.mjs 0, check-governed-queue-guard.mjs --test 0 ('NOT GOVERNED -- 4 path(s) checked against 5 governed surface(s); none matched'). NOT MEASURED, reported as such and not as passes: four check:* names in my first pass returned exit 254 'Command not found' -- my misspellings (check:doc-links, check:doc-fence-languages, check:doc-expression-carriage, check:changeset-*); re-run under their real spellings above. ABLATION, both legs commit-first, mutation proven on disk, restore proven by empty `git diff HEAD` from an EXIT/INT/TERM trap using absolute paths and `git checkout HEAD -- path`: LEG 1 mutated the reporter to [['list'],['html',{open:'never'}]] -- anchor counts 1 to 0 and 'html' 0 to 1, blob 764e69dc to fbe41e11 -- run exit 1, 2 failed / 52 passed, naming 'uploads no path the reporter cannot produce' and 'quotes the reporter value both prose sites are describing'; restored, blob back to 764e69dc, git diff HEAD empty. LEG 2 deleted failure() from the docs warning -- count 1 to 0, blob changed -- run exit 1, 1 failed / 53 passed, naming 'says that a green run uploads nothing at all'; restored, git diff HEAD empty. Direction observed: TURNS RED, and leg 1 failing on both the glob and the prose is the card's fence working (one config edit, three sites flagged). CONTROL-BYTE SELF-SCAN: grep -naP over all 4 changed files together with a file holding a deliberate vertical-tab: exit 0 with the control file as the ONLY hit, so the files are clean and the pattern was live. My first control used a tab, which is not in that byte class and gave a false clean -- exactly the trap the brief names -- redone. LINT, MEASURED NARROWING (three pieces): (1) population read from eslint's own config, eslint.config.js:53 scopes to files:['**/*.{ts,tsx}'] and eslint itself reported the .md and .yml files as ignored; (2) count read from --format json: 4 paths passed, 1 actually linted (ci-cd-pipeline-doc.test.ts), 3 warn-ignored, errorCount 0, exit 0; (3) invariance: no projectService, parserOptions or project: anywhere in eslint.config.js (grep exit 1, control term 'rules' present 11 times), so type-aware linting is not enabled and this diff cannot move any untouched file's verdict. Repo-wide `pnpm lint` is CI's run.",
"changeset": ".changeset/8238-live-e2e-report-promise.md with EMPTY FRONTMATTER -- this repo's declared mechanism for 'this PR publishes nothing'. Confirmed there is no label-based bypass here: ci-cd-pipeline-doc.test.ts actively fails any file under .github/ or scripts/ that introduces one (objectui#4912 option B, declined twice).",
"governed_surface": "NOT governed -- verified mechanically with the guard AGENTS.md names as the authority, not by reading the list: `node scripts/check-governed-queue-guard.mjs --test <4 paths>` exit 0, 'NOT GOVERNED -- 4 path(s) checked against 5 governed surface(s); none matched'. PR left in draft per the dispatch.",
"mcp_calls": "4 -- one targeted search_issues (the dedupe, after the REST channel was denied), one issue_write to file #8561, one create_pull_request, one add_issue_comment for this report. Card body and all comments were read through the zero-MCP REST channel.",
"channel_declaration": "Repo-scoped REST reads work in this container (HTTP 200 for /repos/.../issues/8238 and its /comments), so the card, its triage comment and the claim were all read without MCP. The dedupe search was different: GET /search/issues returned HTTP 403, so I switched to one targeted MCP search_issues, as the escalation rule requires, and declare the change here. Result: total_count 1, only #8238 itself, so #8561 is not a duplicate.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #8561 (labels: `finding` only, no assignee, no domain): ci.yml's E2E artifact is NAMED playwright-report but never contains one -- playwright.config.ts:28 is `reporter: process.env.CI ? 'github' : 'html'`, so on CI it is annotations-only and the artifact holds test-results/ alone. Explicitly NOT a second instance of #8238: the 12-line comment above that step is accurate and complete and is the objectui#4086 prior art that decided this whole question. What remains is only that the artifact NAME is the one surface that comment cannot reach -- it is what a reader sees in the Actions UI and in the downloaded zip. Filed so the rename-or-keep choice is deliberate; left untouched.",
"NOT filed, reported as already-owned: Bundle Analysis is red on main and on every PR that triggers it -- objectui#8542, not from this change and not a required check.",
"NOT filed, reported as already-owned: objectui#7990 (Live E2E informational red on main) is a different, already-filed card and was not folded in."
],
"false_predictions_in_the_brief": [
"'three spellings of one claim' -- the card, the triage comment and the brief all say three. There are FOUR. The uncounted one is the job-summary step at live-e2e.yml:229, which is the text GitHub renders on the run summary page of a failed lane, i.e. the most-read spelling of the false promise. Repairing the other three would have reproduced the defect in the most visible place.",
"'content/docs/guide/ci-cd-pipeline.md was modified at 3f775eeb8 so any line number is stale' -- correct, and confirmed: the sentence is at line 511, not the card's 453.",
"'scripts/__tests__/ci-cd-pipeline-doc.test.ts pins that page -- read it before editing' -- true but not for this claim. It requires a markdown heading naming each workflow and asserts .github/WORKFLOWS.md stays deleted; NO assertion in its 49 pre-existing tests read the report sentence. That is precisely how the sentence drifted. I did not have to work around a pin, I had to add one.",
"'a new gate landed at 3f775eeb8: check:check-action-ref-convention will have an opinion if you touch an action reference' -- it had none here. I changed no action reference; actions/upload-artifact@v7 is untouched. The step's `name:` changed, which that gate does not read. Ran it anyway: exit 0.",
"The card's option 2 as literally worded ('drop playwright-report/ from the upload path') turned out to conflict with a written, reasoned decision in the sibling lane (ci.yml keeps its absent path deliberately). I still dropped it, for a stated reason, rather than following either text silently.",
"The brief's requirement 10 ('os-verify-lock.sh is mode 0644, invoke it as bash <path>') was correct and necessary; noting it held so the next dispatch keeps carrying it."
]
}
ACCEPT — PR #8562. ⭐ It found a FOURTH claim site that this card, its triage comment and this seat's brief all missed — and it is the most-read one.domain:devx @ objectui execution seat, PM session session_01FhBNJcLRZLe8M87VcUgpKr, R47, 2026-09-08T11:25Z. Head 14183b8d5, 4 files.
The fourth site, verified by this seat on origin/main
.github/workflows/live-e2e.yml:230
echo "See the \`live-e2e-artifacts\` upload for the Playwright report and server logs."
⇒ That is the job-summary text GitHub renders on the run summary page of every failed lane. ⭐ Repairing the other three would have reproduced the defect in the most visible place there is. Everyone who looked at this card — the filer, triage, and this seat writing the brief — counted three.
The direction, and why the bigger reason is not the obvious one
Direction taken: make the text true, ⛔ not make the promise true. The axis that decided it is not "an HTML reporter costs a directory":
⭐ This repository already answered this exact question for the sibling lane and wrote the answer down.ci.yml:1372-1383 records that playwright.config.ts uses the github reporter on CI, that the upload step therefore said "No files were found with the provided path: playwright-report/" (objectui#4086), and that the resolution was not to add an HTML reporter but to make the surrounding text true. ⇒ Diverging here would leave two lanes answering one question two ways for no reconstructible reason.
And the defect-class axis: a document declaring a capability the runtime does not deliver. ⭐ "Growing the runtime to match drifted prose rewards the drift" — which is why the PR adds a pin rather than only correcting four sentences.
⭐ Nothing pinned the sentence, which is exactly how it drifted
Verified here: git grep -c -i "playwright.report" over scripts/__tests__/ci-cd-pipeline-doc.test.ts returns 0 (exit 1), with a control in the same run — WORKFLOWS.md returns 4. ⇒ Its 49 pre-existing tests pin the page's headings and the deleted inventory, and not one word of this claim.
⚠️ So the brief's warning — "that test pins the page, read it before editing" — was true but pointed the wrong way: ⛔ there was no pin to work around. There was a pin missing. The dev added one that ties the claim to the mechanism (the reporter list), so it cannot drift again in either direction.
Ablation, both legs commit-first with the mutation proven on disk by blob hash and the restore proven by an empty git diff HEAD under an EXIT INT TERM trap:
adding an html reporter ⇒ 2 failed / 52 passed, naming "uploads no path the reporter cannot produce"and"quotes the reporter value both prose sites are describing" — ⭐ one config edit, three sites flagged, which is the fence working;
deleting failure() from the docs warning ⇒ 1 failed / 53 passed.
The card's second edge, handled rather than dropped
The upload step is gated if: !cancelled() && failure(), so a green lane uploads nothing at all — no artifact, no file count, nothing to read. Recorded in both places a reader would form a criterion, and pinned, so the sentence cannot outlive the gate it describes. ⇒ objectui#8084's criterion (file count above 1 and a playwright-report/ appears) is named on the record as unreachable in its second half.
⚠️ It departed from the card's own option, and said so
The card's option 2 says drop playwright-report/ from the glob. ci.yml argues the opposite for its lane in writing (an absent path costs nothing and keeps working if HTML is ever enabled). The dev dropped it here anyway, with a stated reason: that rationale is sound where it is written down next to the glob, and on live-e2e.yml it was not — an undocumented path in a glob reads to a human as a promise the artefact contains it, which is this card. ⇒ ⛔ Not silent divergence, and ⛔ not silent obedience either.
⚠️ One more control that wasn't a control
The dev's first control-byte self-scan used a tab as the control character — which is not in the scanned byte class, so it produced a false clean. It redid the scan with a file holding a deliberate vertical tab, which then came back as the only hit. ⭐ That is the third distinct shape of this failure today (the exit-128 zero, the ls-tree exit code, and now a control outside the class being controlled), and the dev caught its own.
Verified filings
objectui#8561: ✔ finding only, ⛔ no assignee, ⛔ no domain:*. Correctly scoped — it is about ci.yml's artefact name, ⛔ not a second instance of this card.
⇒ ACCEPT. Ready-flip, guard, queue and landing probe follow the CI verdict.
Landing probe, on a re-fetchedorigin/main — ⚠️ and TWO of its rows were wrong the first time
probe
expect
first read
resolved
the new pin uploads no path the reporter cannot produce
> 0
1
✔
the new pin green run uploads nothing at all
> 0
1
✔
⭐ firing control — live-e2e in the test file
> 0
29
✔
⭐ nonsense control — zzq-not-a-real-token-8238
0
0 (exit 1)
✔
⚠️playwright-report/ in live-e2e.yml
0
2
⇒ both are comment lines
⚠️uploaded Playwright report in the docs
0
1
⇒ the new paragraph quoting the old claim
The two, resolved structurally rather than by substring
playwright-report/ survives in live-e2e.ymlonly at :240 and :243, inside a fence the fix added:
# ⛔ Do not add `playwright-report/` back to this glob without changing
# … reporter in it writes `playwright-report/`, so the path matched nothing
The glob itself (:260-263) now reads test-results/ plus the two server logs — the path is gone. And the docs hit at :519 sits inside the new paragraph that says the artefact carries no Playwright HTML report and records that "this page and the workflow header both promised" one.
⇒ ⭐ A fix that documents what it removes will always contain the string a naive negative probe searches for. This is the sixth encounter today with that family, and the third where this seat's own probe was the thing at fault. The probe that answers the question is the structural one: read the glob, ⛔ not the file.
What is now on main
All four claim sites moved together — the workflow header, the job-summary echo (the site the card, its triage comment and this seat's brief all missed, and the one GitHub renders on every failed run's summary page), the upload glob, and the docs section. And the claim is now pinned to its mechanism: the test asserts the prose against the reporter list, so ⛔ neither can drift without the other going red.
The card's second edge is on the record too: the upload step is gated if: !cancelled() && failure(), so a green lane uploads nothing at all — which is why objectui#8084's acceptance criterion (file count above 1 and a playwright-report/ appears) was unsatisfiable in its second half.
⚠️ Predicted and confirmed: this did NOT fix objectui#8561's surface
As stated on that card before this landed, and re-measured on f5cfbbd81:
.github/workflows/ci.yml still carries 4playwright-report/ references — deliberately, with the reasoned 12-line comment objectui#4086 left there;
content/docs/guide/ci-cd-pipeline.md:217 still reads "Uploads the Playwright report on failure" for the e2e lane — 1 hit, unchanged.
⇒ objectui#8561 remains open with two surfaces, ⛔ neither touched here, and the rename-or-keep decision is still one decision for both.
Activity
os-zhuang commentedon Sep 8, 2026
Triage routing
Labels
bugci/cddocumentationfindingtoolingdomain:devxpm:queuepriority:p3· Type BugLane —
domain:devx. All three surfaces are the engineering face:.github/workflows/live-e2e.yml,playwright.live.config.ts, andcontent/docs/guide/ci-cd-pipeline.md. The docs file goes to devx here rather than todomain:uibecause the face it records is the CI lane, not a component authoring surface.Re-measured on
83d4fda16— all three anchors confirmed.playwright.live.config.ts:31—reporter: [['list']],and nothing else. Thelistreporter writes to stdout; no reporter in that config writesplaywright-report/..github/workflows/live-e2e.yml:240-241— the upload glob isplaywright-report/+test-results/.content/docs/guide/ci-cd-pipeline.md:453— "Failures still surface as a red step plus an uploaded Playwright report and job summary."So the first line of the glob matches nothing in every outcome — pass, fail, or crash. That is not an inference; it follows from the config, and the card additionally measured it twice against a real backend with both a failing and a passing run,
playwright-report/ABSENT in both. Two runs with opposite Playwright outcomes and the same absence is the right shape of evidence for this claim.Admission — class (b), violation of a declared contract. Two published documents and a workflow step assert an artifact the configuration cannot produce.
Grade p3. Nothing is broken in CI itself — the lane runs, fails red, and uploads what it actually has. The cost is misdirection, which is real but bounded.
⛔ The card's second finding is the load-bearing one and must not be dropped in a wording tidy-up. The upload step is gated
if: ${{ !cancelled() && failure() }}, so on a green lane it does not run at all — no artifact, no file count to read. Any acceptance criterion phrased in terms of the artifact's contents is therefore readable only on a run that failed. That is a general trap about how this lane can be checked, and it outlives whichever option is chosen here.Its concrete casualty is already on the record: objectui#8084's dispatch defined "the fix is real" as the uploaded artifact's file count goes above 1 and a
playwright-report/appears. The first half is sound; the second half is unreachable, so a correct fix could never satisfy the stated test. ⛔ Whoever works #8084 must not treat that criterion as still standing — an acceptance criterion nobody can pass is the same hazard as a check nobody can fail, pointed the other way. I am recording that here rather than editing #8084's dispatch text, which is not this seat's to rewrite.Both options are determinate — not a decision-box card.
['html', { open: 'never' }]to the live config's reporter list. Costs a directory per run; makes the glob mean what it says.playwright-report/from the glob and reword both sentences to name what is really uploaded (test-results/,backend.log,console-preview.log).Neither widens an accepted set nor deletes a shipped capability, so a claimant picks. State which in the claim comment.
⛔ Fence, adopted from the card and binding either way: the workflow header comment, the
ci-cd-pipeline.md:453sentence, and the upload glob are three spellings of one claim and must move together. This card exists precisely because they drifted from the config that decides them; a fix that repairs two of three reproduces the defect in a smaller place.Also check, do not assume: the card flags that
playwright.config.ts(the non-live one) should be examined for the same question. Read it; if it has an HTML reporter the two configs disagree, and if it does not, the same claim may exist on another lane. ⛔ Do not extend the fix to that lane without saying so — report what you find and file separately if it is a second instance.Not in scope here — objectui#8084's defect ② (the workflow RUN reporting
successwhile the JOB reportsfailure) is a different mechanism and stays on that card.Triage seat only — no claim, no dispatch, no code.
Generated by Claude Code
claude commentedon Sep 8, 2026
Claim: PM loop round R47 —
domain:devx @ objectuiexecution seat.Session:
session_01FhBNJcLRZLe8M87VcUgpKrBranch:
claude/issue-8238-live-e2e-report-promiseWorktree:
/home/user/objectui-issue-8238Domain:
domain:devxDispatch: one
os-devsubagent of this session (mode:subagent,model: opus); the dispatch note with the brief follows. Labelspm:queue→pm:dispatchedand assignee set in one write, read back and diffed against the union at 2026-09-08T10:54:19Z.Why this card now — the total order, stated because the answer is counter-intuitive
The lane's written order is
priority:p0>pm:blocking>target:> typeBug> the rest, tie-broken by card age. ⭐ Belowp0, the priority label does not feature in that order. Re-derived at 10:53Z: nop0, nopm:blockingat all, notarget:items ⇒ the live level is typeBug, and the queue holds 30 dispatchable cards it cannot empty this round ⇒ oldest first. This card is among the oldest Bugs in the lane.priority:p1. ⛔ Under the written order p1 does not rank; the justification that actually holds is a different rule — an advisory gate red onmainis shared damage, to be stopped by whichever lane finds it. ⇒ Right card, ⛔ wrong reason given, corrected here.p1+pm:queueand is already fixed by83d4fda16(main's CI is green again; the proof is on that card). ⇒ Skipped by the premise check, ⛔ not dispatched.not a git repository), which prints as no output and reads exactly like a clean zero.⭐ Filing rule: a concrete defect carries no labels at all; an observation carries
findingonly. ⛔ Never adomain:*, ⛔ never an assignee.Generated by Claude Code
baozhoutao commentedon Sep 8, 2026
os-dev-report
{ "issue": 8238, "status": "done", "branch": "claude/issue-8238-live-e2e-report-promise", "pr": "https://github.com/objectstack-ai/objectui/pull/8562", "head_sha": "14183b8d52a5f01a8251b9bbf65d7edf094e8af6", "premise_still_valid": true, "summary": "Re-measured all three anchors on a re-fetched origin/main (3f775eeb8) and confirmed every one: playwright.live.config.ts:31 is `reporter: [['list']],` and nothing else, the live-e2e.yml upload glob is playwright-report/ + test-results/, and ci-cd-pipeline.md promises 'an uploaded Playwright report'. Then found a FOURTH claim site the card, the triage comment and the brief all missed: the job-summary step at live-e2e.yml:229 echoes 'See the live-e2e-artifacts upload for the Playwright report and server logs' -- the single most-read spelling of the false promise, since it renders on the run summary page of every failed lane. Direction taken: MAKE THE TEXT TRUE, all four sites moved together, plus a new pin block in scripts/__tests__/ci-cd-pipeline-doc.test.ts that ties the claim to the mechanism (the reporter list) so it cannot drift again in either direction. Also recorded and pinned the card's second, sharper edge: the upload step is gated on failure(), so a green lane uploads nothing at all and any acceptance criterion phrased over the artefact's contents is readable only on a red run. Filed one observation as #8561 and did not repair it.", "direction_taken": "make the text true (not: make the promise true)", "why_that_direction": "Four-axis analysis, all four agreeing, and the third is what made the PR bigger than a wording tidy-up. (1) REAL BUSINESS NEED: a reader of a red run needs the failure evidence, which use.screenshot/use.video already write into test-results/; the HTML report is a viewer over that same data, not a second source. This lane sets trace:'on-first-retry' with retries:0, so traces are never written here at all -- the report would have had less to show than on a retrying lane. Zero measured pull: nobody has ever consumed a report here because none has ever existed. (2) LONG-TERM ARCHITECTURE: this repository already answered this exact question for the sibling lane and wrote the answer into ci.yml:1370-1383 -- playwright.config.ts uses the `github` reporter on CI, which produces no playwright-report/, the upload step said 'No files were found with the provided path' (objectui#4086), and the resolution was NOT to add an HTML reporter but to make the surrounding text true. Diverging would leave two lanes answering one question two ways for no reconstructible reason. (3) PREVENTING AI ERRORS -- the deciding axis: the defect class is a document declaring a capability the runtime does not deliver. Growing the runtime to match drifted prose rewards the drift and is the moral equivalent of a tolerant consumer-side fallback; the structural answer is to make the declaration CHECKABLE, which is why this PR adds a pin rather than only correcting four sentences. (4) STARTUP-STAGE NON-PROLIFERATION: an HTML reporter is a capability expansion with no named consumer, costing a directory per run on an informational lane that cannot block a merge -- implementation-first with no puller means the default is tight.", "evidence_promise_now_matches_reality": "Ran the reporters rather than reading the config, three legs with a control. Leg A, reporter [['list']] (the live config's actual value), 1 passed, playwright exit 0: playwright-report/ ABSENT, test-results/ 1 file. Leg B, same reporter, 1 failed, exit 1: playwright-report/ ABSENT, test-results/ 2 files. CONTROL leg, reporter [['html',{open:'never'}]], 1 passed, exit 0: playwright-report/ PRESENT (1 file). The control is what makes the two absences readable -- without a run that does produce the directory, ABSENT is indistinguishable from a probe that cannot see directories. Card's two-run measurement against a real backend reproduces exactly. All four prose/config sites now describe this: workflow header, job-summary step, upload glob (playwright-report/ removed), and the docs page's Live E2E section.", "departure_from_the_card_option": "The card's option 2 says to drop playwright-report/ from the glob; ci.yml argues the opposite for its own lane (upload-artifact only warns when NO path matches, so an absent path costs nothing and keeps working if HTML is ever enabled). I dropped it here anyway: that rationale is sound where it is written down next to the glob, and on ci.yml it is -- on live-e2e.yml it was not, and an undocumented path in a glob is read by humans as a promise the artefact contains it, which is this card. The 'if it is ever enabled' property is speculative capability, declined under axis 4. Did not touch ci.yml; its comment is truthful and its lane is not mine.", "second_edge": "Handled, not dropped. The upload step is gated `if: not-cancelled and failure()`, so on a GREEN lane it does not run at all -- no artifact, no file count, nothing to read. Recorded in the two places a reader would form such a criterion (a warning block in the docs page's Live E2E section, and a comment on the upload step) AND pinned by the new test 'says that a green run uploads nothing at all', which requires the docs section to mention failure() and requires the upload step to actually carry that gate -- so the sentence cannot outlive the gate it describes. The concrete casualty is named on the record: objectui#8084's criterion (file count above 1 AND a playwright-report/ appears) has an unreachable second half, so a correct fix could never have satisfied its stated test. Did not touch #8084's defect 2 (run reports success while job reports failure) -- different mechanism, stays on that card.", "tests": "ALL exit codes captured by redirect before any pipe. UNIT: `vitest run --project unit scripts/__tests__/ci-cd-pipeline-doc.test.ts` exit 0, 54 passed. Plus the 3 neighbouring suites that read live-e2e.yml (ensure-chromium-ready, workflow-cache-save-bound, vitest-invocation-guard): 4 files, 111 passed, wrapper VERDICT command-exit 0. GATES (real spellings): check:action-ref-convention 0, check:control-bytes 0, check:shell-escape-residue 0, type-check:scripts 0, check-doc-links.mjs 0 ('Links are valid across 17 scan roots'), check-doc-fence-languages.mjs 0, check-doc-expression-carriage.mjs 0 (report-only gate), check-changeset-presence.mjs 0 ('No source or published contract of a released package changed in this range, so no changeset is owed'), check-changeset-no-major.mjs 0, check-changeset-overwrite.mjs 0, check-governed-queue-guard.mjs --test 0 ('NOT GOVERNED -- 4 path(s) checked against 5 governed surface(s); none matched'). NOT MEASURED, reported as such and not as passes: four check:* names in my first pass returned exit 254 'Command not found' -- my misspellings (check:doc-links, check:doc-fence-languages, check:doc-expression-carriage, check:changeset-*); re-run under their real spellings above. ABLATION, both legs commit-first, mutation proven on disk, restore proven by empty `git diff HEAD` from an EXIT/INT/TERM trap using absolute paths and `git checkout HEAD -- path`: LEG 1 mutated the reporter to [['list'],['html',{open:'never'}]] -- anchor counts 1 to 0 and 'html' 0 to 1, blob 764e69dc to fbe41e11 -- run exit 1, 2 failed / 52 passed, naming 'uploads no path the reporter cannot produce' and 'quotes the reporter value both prose sites are describing'; restored, blob back to 764e69dc, git diff HEAD empty. LEG 2 deleted failure() from the docs warning -- count 1 to 0, blob changed -- run exit 1, 1 failed / 53 passed, naming 'says that a green run uploads nothing at all'; restored, git diff HEAD empty. Direction observed: TURNS RED, and leg 1 failing on both the glob and the prose is the card's fence working (one config edit, three sites flagged). CONTROL-BYTE SELF-SCAN: grep -naP over all 4 changed files together with a file holding a deliberate vertical-tab: exit 0 with the control file as the ONLY hit, so the files are clean and the pattern was live. My first control used a tab, which is not in that byte class and gave a false clean -- exactly the trap the brief names -- redone. LINT, MEASURED NARROWING (three pieces): (1) population read from eslint's own config, eslint.config.js:53 scopes to files:['**/*.{ts,tsx}'] and eslint itself reported the .md and .yml files as ignored; (2) count read from --format json: 4 paths passed, 1 actually linted (ci-cd-pipeline-doc.test.ts), 3 warn-ignored, errorCount 0, exit 0; (3) invariance: no projectService, parserOptions or project: anywhere in eslint.config.js (grep exit 1, control term 'rules' present 11 times), so type-aware linting is not enabled and this diff cannot move any untouched file's verdict. Repo-wide `pnpm lint` is CI's run.", "changeset": ".changeset/8238-live-e2e-report-promise.md with EMPTY FRONTMATTER -- this repo's declared mechanism for 'this PR publishes nothing'. Confirmed there is no label-based bypass here: ci-cd-pipeline-doc.test.ts actively fails any file under .github/ or scripts/ that introduces one (objectui#4912 option B, declined twice).", "governed_surface": "NOT governed -- verified mechanically with the guard AGENTS.md names as the authority, not by reading the list: `node scripts/check-governed-queue-guard.mjs --test <4 paths>` exit 0, 'NOT GOVERNED -- 4 path(s) checked against 5 governed surface(s); none matched'. PR left in draft per the dispatch.", "mcp_calls": "4 -- one targeted search_issues (the dedupe, after the REST channel was denied), one issue_write to file #8561, one create_pull_request, one add_issue_comment for this report. Card body and all comments were read through the zero-MCP REST channel.", "channel_declaration": "Repo-scoped REST reads work in this container (HTTP 200 for /repos/.../issues/8238 and its /comments), so the card, its triage comment and the claim were all read without MCP. The dedupe search was different: GET /search/issues returned HTTP 403, so I switched to one targeted MCP search_issues, as the escalation rule requires, and declare the change here. Result: total_count 1, only #8238 itself, so #8561 is not a duplicate.", "open_questions": [], "out_of_scope_findings": [ "filed as #8561 (labels: `finding` only, no assignee, no domain): ci.yml's E2E artifact is NAMED playwright-report but never contains one -- playwright.config.ts:28 is `reporter: process.env.CI ? 'github' : 'html'`, so on CI it is annotations-only and the artifact holds test-results/ alone. Explicitly NOT a second instance of #8238: the 12-line comment above that step is accurate and complete and is the objectui#4086 prior art that decided this whole question. What remains is only that the artifact NAME is the one surface that comment cannot reach -- it is what a reader sees in the Actions UI and in the downloaded zip. Filed so the rename-or-keep choice is deliberate; left untouched.", "NOT filed, reported as already-owned: Bundle Analysis is red on main and on every PR that triggers it -- objectui#8542, not from this change and not a required check.", "NOT filed, reported as already-owned: objectui#7990 (Live E2E informational red on main) is a different, already-filed card and was not folded in." ], "false_predictions_in_the_brief": [ "'three spellings of one claim' -- the card, the triage comment and the brief all say three. There are FOUR. The uncounted one is the job-summary step at live-e2e.yml:229, which is the text GitHub renders on the run summary page of a failed lane, i.e. the most-read spelling of the false promise. Repairing the other three would have reproduced the defect in the most visible place.", "'content/docs/guide/ci-cd-pipeline.md was modified at 3f775eeb8 so any line number is stale' -- correct, and confirmed: the sentence is at line 511, not the card's 453.", "'scripts/__tests__/ci-cd-pipeline-doc.test.ts pins that page -- read it before editing' -- true but not for this claim. It requires a markdown heading naming each workflow and asserts .github/WORKFLOWS.md stays deleted; NO assertion in its 49 pre-existing tests read the report sentence. That is precisely how the sentence drifted. I did not have to work around a pin, I had to add one.", "'a new gate landed at 3f775eeb8: check:check-action-ref-convention will have an opinion if you touch an action reference' -- it had none here. I changed no action reference; actions/upload-artifact@v7 is untouched. The step's `name:` changed, which that gate does not read. Ran it anyway: exit 0.", "The card's option 2 as literally worded ('drop playwright-report/ from the upload path') turned out to conflict with a written, reasoned decision in the sibling lane (ci.yml keeps its absent path deliberately). I still dropped it, for a stated reason, rather than following either text silently.", "The brief's requirement 10 ('os-verify-lock.sh is mode 0644, invoke it as bash <path>') was correct and necessary; noting it held so the next dispatch keeps carrying it." ] }Generated by Claude Code
claude commentedon Sep 8, 2026
ACCEPT — PR #8562. ⭐ It found a FOURTH claim site that this card, its triage comment and this seat's brief all missed — and it is the most-read one.
domain:devx @ objectuiexecution seat, PM sessionsession_01FhBNJcLRZLe8M87VcUgpKr, R47, 2026-09-08T11:25Z. Head14183b8d5, 4 files.The fourth site, verified by this seat on
origin/main⇒ That is the job-summary text GitHub renders on the run summary page of every failed lane. ⭐ Repairing the other three would have reproduced the defect in the most visible place there is. Everyone who looked at this card — the filer, triage, and this seat writing the brief — counted three.
The direction, and why the bigger reason is not the obvious one
Direction taken: make the text true, ⛔ not make the promise true. The axis that decided it is not "an HTML reporter costs a directory":
⭐ This repository already answered this exact question for the sibling lane and wrote the answer down.
ci.yml:1372-1383records thatplaywright.config.tsuses thegithubreporter on CI, that the upload step therefore said "No files were found with the provided path: playwright-report/" (objectui#4086), and that the resolution was not to add an HTML reporter but to make the surrounding text true. ⇒ Diverging here would leave two lanes answering one question two ways for no reconstructible reason.And the defect-class axis: a document declaring a capability the runtime does not deliver. ⭐ "Growing the runtime to match drifted prose rewards the drift" — which is why the PR adds a pin rather than only correcting four sentences.
⭐ Nothing pinned the sentence, which is exactly how it drifted
Verified here:
git grep -c -i "playwright.report"overscripts/__tests__/ci-cd-pipeline-doc.test.tsreturns 0 (exit 1), with a control in the same run —WORKFLOWS.mdreturns 4. ⇒ Its 49 pre-existing tests pin the page's headings and the deleted inventory, and not one word of this claim.Ablation, both legs commit-first with the mutation proven on disk by blob hash and the restore proven by an empty
git diff HEADunder anEXIT INT TERMtrap:htmlreporter ⇒ 2 failed / 52 passed, naming "uploads no path the reporter cannot produce" and "quotes the reporter value both prose sites are describing" — ⭐ one config edit, three sites flagged, which is the fence working;failure()from the docs warning ⇒ 1 failed / 53 passed.The card's second edge, handled rather than dropped
The upload step is gated
if: !cancelled() && failure(), so a green lane uploads nothing at all — no artifact, no file count, nothing to read. Recorded in both places a reader would form a criterion, and pinned, so the sentence cannot outlive the gate it describes. ⇒ objectui#8084's criterion (file count above 1 and aplaywright-report/appears) is named on the record as unreachable in its second half.The card's option 2 says drop
playwright-report/from the glob.ci.ymlargues the opposite for its lane in writing (an absent path costs nothing and keeps working if HTML is ever enabled). The dev dropped it here anyway, with a stated reason: that rationale is sound where it is written down next to the glob, and onlive-e2e.ymlit was not — an undocumented path in a glob reads to a human as a promise the artefact contains it, which is this card. ⇒ ⛔ Not silent divergence, and ⛔ not silent obedience either.The dev's first control-byte self-scan used a tab as the control character — which is not in the scanned byte class, so it produced a false clean. It redid the scan with a file holding a deliberate vertical tab, which then came back as the only hit. ⭐ That is the third distinct shape of this failure today (the exit-128 zero, the
ls-treeexit code, and now a control outside the class being controlled), and the dev caught its own.Verified filings
objectui#8561: ✔
findingonly, ⛔ no assignee, ⛔ nodomain:*. Correctly scoped — it is aboutci.yml's artefact name, ⛔ not a second instance of this card.⇒ ACCEPT. Ready-flip, guard, queue and landing probe follow the CI verdict.
Generated by Claude Code
claude commentedon Sep 8, 2026
LANDED —
e411c3e58onorigin/main.domain:devx @ objectuiexecution seat, PM sessionsession_01FhBNJcLRZLe8M87VcUgpKr, R47, 2026-09-08T12:40Z. PR #8562 (card objectui#8238), merged 12:20:36Z.Landing probe, on a re-fetched⚠️ and TWO of its rows were wrong the first time
origin/main—uploads no path the reporter cannot producegreen run uploads nothing at alllive-e2ein the test filezzq-not-a-real-token-8238playwright-report/inlive-e2e.ymluploaded Playwright reportin the docsThe two, resolved structurally rather than by substring
playwright-report/survives inlive-e2e.ymlonly at:240and:243, inside a fence the fix added:The glob itself (
:260-263) now readstest-results/plus the two server logs — the path is gone. And the docs hit at:519sits inside the new paragraph that says the artefact carries no Playwright HTML report and records that "this page and the workflow header both promised" one.⇒ ⭐ A fix that documents what it removes will always contain the string a naive negative probe searches for. This is the sixth encounter today with that family, and the third where this seat's own probe was the thing at fault. The probe that answers the question is the structural one: read the glob, ⛔ not the file.
What is now on
mainAll four claim sites moved together — the workflow header, the job-summary echo (the site the card, its triage comment and this seat's brief all missed, and the one GitHub renders on every failed run's summary page), the upload glob, and the docs section. And the claim is now pinned to its mechanism: the test asserts the prose against the reporter list, so ⛔ neither can drift without the other going red.
The card's second edge is on the record too: the upload step is gated
if: !cancelled() && failure(), so a green lane uploads nothing at all — which is why objectui#8084's acceptance criterion (file count above 1 and aplaywright-report/appears) was unsatisfiable in its second half.As stated on that card before this landed, and re-measured on
f5cfbbd81:.github/workflows/ci.ymlstill carries 4playwright-report/references — deliberately, with the reasoned 12-line comment objectui#4086 left there;content/docs/guide/ci-cd-pipeline.md:217still reads "Uploads the Playwright report on failure" for thee2elane — 1 hit, unchanged.⇒ objectui#8561 remains open with two surfaces, ⛔ neither touched here, and the rename-or-keep decision is still one decision for both.
Generated by Claude Code
test(ci-cd-doc): read the Playwright reporter through the shared comm…