I wanted Codex, my local coding agent, to ask the separate ChatGPT page in my browser for a second opinion. Give it a draft or PDF, use my signed-in conversation, and bring the review back. The first prototype proved the round trip was possible. The next challenge was knowing whether it had actually succeeded.
The replacement is a narrow local bridge. In the September 3, 2026 acceptance run, two different 77-page synthetic PDFs passed 12 and 8 source/page checks: first in a new conversation, then in that same conversation. Each task made one browser submission attempt, and the bridge confirmed one matching user turn. Both returned checked JSON without read-only recovery. Those observations are not an exactly-once delivery guarantee.
Here, verified extraction means the bridge’s identity, completion and declared text checks passed. JSON validity and factual correctness are separate checks performed by the caller. A verified extraction is not an endorsement of ChatGPT’s answer.
The main article explains the architecture, its contract and what the failures changed in the design. The debugging reference collects the individual bugs, workarounds, failed experiments and regression evidence.
2026-09-25 中文学习补记: 如果你想知道最近到底在排查什么、为什么修复会反复,可以直接读第 9 节:这轮调试是怎样推进的,再看第 10 节:接下来怎样减少反复。原有架构说明和逐项 reference 保留,便于对照代码与证据。
flowchart TD
C["Codex + skill<br/>Choose the question"]
M["MCP adapter<br/>Expose one local tool"]
W["WebContentAgent<br/>Validate and record"]
X["Chrome Companion<br/>Operate the page"]
G["Signed-in ChatGPT<br/>Generate the answer"]
S["Private task records<br/>Saved session URLs"]
C <--> M <--> W <--> X <--> G
W <--> S
Figure 1. A logical component view, not a complete process or network diagram. The local agent keeps task state and saved destinations; the extension initiates polling. Section 2 follows these boundaries through one request.
1. A small tool with a specific contract
The user-facing request is ordinary language:
Send this PDF to GPT and ask for a critical review.
Use our existing conversation.
The input is the exact question, an optional allowed PDF, and an intended conversation. The output is that request’s answer and its saved conversation link—or an explicit failure. An unrelated active tab, an older answer and a plausible-looking fragment are not substitutes.
An allowed PDF is a regular .pdf file whose resolved path is inside a configured directory and whose size is below the configured limit. File selection is an input-validation decision, not permission to upload unrelated files.
The division of responsibility is deliberately small:
| Component | Owns | Does not own |
|---|---|---|
| Codex and the skill | Decide what to ask, recognize authorization, evaluate the review | Rediscover the page procedure on every task |
| Node MCP adapter | Expose the tool and connect it to local code | Operate Chrome or decide whether an answer is correct |
| WebContentAgent | Validate inputs, store task state, coordinate work and verify results | Generate the ChatGPT answer |
| Chrome Companion | Reuse the right tab, stage one request, observe submission and extract its reply | Invent a new task or retry an uncertain send |
MCP means Model Context Protocol, the interface through which Codex calls the tool. WebContentAgent is the local Python program; despite its name, this route is a fixed procedure, not another language model reasoning about the page. Chrome Companion is an extension in my normal signed-in Chrome profile.
Those four components run locally. Sending a question or PDF still transmits it to the remote ChatGPT service. “Local bridge” describes the orchestration, not an offline or private-model inference path.
That separation made fixes reusable. A better attachment selector belongs in code and a regression test. The skill should explain when to call that code and how to interpret its result—not become a growing script of browser instructions.
2. Follow one PDF through the architecture
Suppose I have revised this article and want another review in the conversation used last time.
Resolve the request. Codex calls ask_chatgpt with the question, PDF path and saved session name. A local registry maps that readable name to the conversation’s final saved address, called its canonical URL: https://chatgpt.com/c/<conversation-id>. The agent validates the destination and file, then writes a private task record before browser work begins.
Cross the local boundary. The Node adapter receives the MCP call over standard input/output and starts a Python child process, passing the request as JSON through its input stream. That process calls WebContentAgent’s bridge code directly and hosts the local HTTP listener. Chrome Companion polls that listener for an eligible task and posts observations and results back, using a local pairing token. The listener accepts connections on the same machine, or loopback. HTTP connects the extension to Python; it is not the transport between the Node and Python adapters.
Reuse the correct tab. Companion looks for an eligible open tab whose normalized URL exactly matches the intended conversation. A previously managed tab is revalidated. It opens a new tab only when no eligible match exists; whichever ChatGPT tab happens to be active is not automatically the destination.
Stage and send. The extension preserves existing prompt drafts, fills an empty composer, attaches the approved PDF through Chrome’s file-input interface, and checks the current Send form. A selected file is not yet proof of a ready attachment. Existing draft text or attachments block the operation instead of being silently removed. After rechecking the destination and obtaining a single-use send permit, it attempts submission once.
Record and read. The extension must observe the new user turn, its stable message ID, exact prompt body and expected attachment. The agent persists that acknowledgement before starting a separate response-wait budget. Time spent uploading a large PDF has not already consumed the whole answer-reading budget.
The extension then watches the assistant turn associated with that recorded user message. Once the candidate satisfies completion and caller-supplied text constraints, the local agent checks the returned payload again and sends it back to Codex.
The record preserves distinctions that a single success flag would hide: the click ran; the page displayed a new message; the bridge confirmed that message; its answer passed extraction checks. Each is a different observation.
Concurrent writes are not a supported workflow. A bridge instance rejects a second request while one is active, and Companion’s ChatGPT polling lane runs one task at a time. This is not a transactional conversation lock across arbitrary clients. Callers should schedule requests sequentially, not race two agents against the same composer.
3. The send boundary and safe recovery
A timeout is not evidence that nothing happened. ChatGPT may accept a message just before the extension loses its acknowledgement.
The bridge therefore promises at most one browser submission attempt per task, not exactly-once delivery inside ChatGPT. Before the single-use permit is granted, cancellation can prevent sending. After the grant, cancellation can race the page action. Delivery remains uncertain until a valid acknowledgement arrives.
The operation distinguishes three outcomes:
- Zero submission proven: authoritative state establishes that no submission occurred. A later authorized retry may be safe.
- Submission confirmed: the intended conversation, stable user-message ID, prompt and any expected attachment were correlated.
- Delivery uncertain: confirmation is missing and zero submission was not proven. Do not resend automatically.
flowchart TD
A["Validate and stage"] --> B["Grant single-use permit"]
B --> C["Attempt send once"]
C --> D{"Original user message<br/>and destination recorded?"}
D -->|"Yes"| E["Wait for its checked reply"]
D -->|"No: delivery uncertain"| F["Stop; do not resend"]
E -->|"Pass"| G["Return result"]
E -->|"Eligible extraction failure"| H["One read-only recovery<br/>bound to that message"]
H -->|"Pass"| G
H -->|"Fail"| F
Figure 2. This diagram follows the post-permit path; a pre-permit rejection can instead prove zero submission. Once an attempt may have occurred, a missing acknowledgement means uncertainty. Only a confirmed message identity enables automatic read recovery.
If extraction fails after the conversation and user-message ID have been confirmed, the bridge can make one automatic read-only recovery. It looks for the answer to that original message. It does not simply pick the latest visible reply. Without the confirmed identity, automatic recovery stops; maintenance can continue read-only diagnosis as described in AI.
Persistent duplicate fingerprints and a caller-supplied operation key add protection across repeated tool calls. They do not make a browser action transactional. In particular, message_submitted: false means “not confirmed,” not necessarily “not sent.” A retry of a recorded task needs affirmative zero-submission proof in its matching durable terminal record, with no conflicting permit or send evidence. A validation failure before any task exists needs the separate no-record check in AJ. A single reported flag is not enough.
This is the main architectural lesson: uncertainty must remain visible instead of becoming an excuse to send again.
4. Verify the round trip, then the answer
Several checks answer different questions:
| Layer | Question | Evidence |
|---|---|---|
| Submission | Did this request appear in this conversation? | Witnessed new user turn, stable ID, exact prompt, scoped attachment and destination checks |
| Extraction | Is this the associated finished answer? | Message binding, current-turn completion controls, generation state and stable text |
| Output contract | Does the answer have the requested form? | Minimum length, exact-copy metrics, or strict parsing and field checks |
| Content | Is the answer supported by the document? | Independent source/page checks or editorial judgment |
For exact-copy tests, the caller can supply the expected normalized character count, line count and SHA-256. For a review, a minimum length can reject a one-word prefix, but cannot prove that every paragraph arrived. A hash of an unknown answer merely fingerprints what was extracted.
Structured output needs another check. A completed response can still be invalid JSON. The original extracted reply must parse strictly and satisfy the requested fields; repairing it afterward does not turn the original run into a pass.
Two failures made these layers concrete. A background page could stop updating the text the bridge could read even though a later inspection showed an answer. Separately, inline citation controls could insert layout newlines into otherwise valid JSON. One was a rendering problem; the other was extraction of interface decoration. Neither could be fixed by asking GPT to “be more careful.” The reference explains both.
5. What the PDF acceptance runs established
Two fresh Codex test tasks exercised the installed bridge on September 3. A controller then checked their original outputs against the source PDFs and private task records. Both records showed actual Companion 0.4.45, not just that version in a source file. The relevant runtime and installed skill stayed unchanged between the passes.
These are the PDF acceptance runs. A later text-only failure exposed a different boundary and led to 0.4.47; Reference Y records that repair and its separate live checks.
Here, “independent” means a separate controller recomputed the checks rather than trusting the tester’s answer key or PASS label. It does not mean an external replication, a blinded human study or statistically independent samples.
| Scenario | Original returned JSON | Source/page checks | |
|---|---|---|---|
| New conversation | 77 pages, 58.15 MB | 664 non-newline characters | 12/12 |
| Different PDF, same conversation | 77 pages, 61.03 MB | 2,272 non-newline characters | 8/8 |
Sizes use decimal MB. These are different checklists, not comparable quality scores. Both runs recorded one browser attempt, one confirmed submission, verified extraction, no read recovery and rendering assistance. The 20 checks concern selected facts and pages, not exhaustive coverage of either document. “No recovery” does not mean no browser intervention.
The first document tested tables, joins across pages, signed offsets, revision precedence and an image-only token with shape counts. The token was absent from the PDF text layer. The second tested different facts and raster figures: a bar chart, a route that excluded a debug branch, and a late-page counter.
The second pass, round 006, also directly tested the latest repair. Eight file-citation groups appeared inside JSON reasons. A read-only audit matched their page metadata and identified the newly attached document through the normal citation popup. In that post-fix run, raw page innerText still failed JSON parsing, but the bridge’s unchanged returned text passed. The first pass had suppressed citations, so it could not establish that repair by itself. The earlier round-004 format failure remains a separate failed run.
These results meet the defined new-conversation plus reused-conversation acceptance condition. They do not establish exhaustive understanding of 154 pages, a general success rate, or a successful factual review of the user’s original game PDF. The documents were controlled, text-and-image-rich test cases, not a benchmark of arbitrary scanned PDFs.
That acceptance is a dated result, not a blanket all-clear. A later editorial-review attempt using an older saved conversation failed its destination check before sending. The bridge proved zero submission; the cause remains undiagnosed. Reference Q records that boundary.
6. The lesson and the limits
Moving the procedure into local code made it easier to test, but did not make its assumptions correct. The failures came from both design mistakes and missed boundary cases. The records do not support blaming all of them on ChatGPT changing its page. Several were ordinary errors in our own input handling, state management and tests.
Presentation was doing the job of identity
The early implementation used convenient observations as substitutes for stronger evidence: a larger message count meant a new turn, a filename meant an upload, and an old Copy button plus stable text meant a finished answer. Each shortcut could succeed on a small demonstration. Each also confused what the page happened to display with what this request had done.
The repair was to give those questions separate answers. A stable user-message ID identifies the request; exact prompt and scoped attachment checks correlate it with the intended input; controls on its associated assistant turn contribute completion evidence. Adding more waiting time would not make a history card belong to the current form or make an older reply answer the new question.
| Broken assumption | Design change | Details |
|---|---|---|
| Counts and whole-page text identify this request | Bind checks to a witnessed message and its fields | B–G |
| Plausible text satisfies every output contract | Separate authored content, extraction and caller constraints | J, U |
| A running wait loop will survive until completion | Persist phase and identity; resume only a confirmed read | S–T |
| An empty inspection means the page needs more time | Separate loading, invalid results and execution errors | R, Y |
| A stricter guard only removes bad outcomes | Test legitimate transitions alongside forbidden ones | A, N |
| Passing mocks exercise the deployed boundary | Run the real helper, wrapper and relevant browser mechanism | I, Y |
| One URL spelling or raw rendered text preserves identity | Normalize supported routes and faithfully serialize the scoped prompt | AH, AI |
A browser workflow crosses process lifetimes
The extension worker, Python listener and Codex caller can stop independently. One incident left a completed page behind a restarted worker that could no longer claim the task. Another let Python close its listener while the extension was still within its own response deadline. These were protocol-design gaps: the components disagreed about who owned unfinished work and when its time was up.
The resulting design persists the confirmed message before waiting for its answer, gives preparation and response separate agreed budgets, and lets a restarted worker continue a read without receiving send arguments. Cancellation follows the same principle: stopping a process cannot undo an already granted send permit. Durable state must describe what may have happened, even when the caller would prefer a simple failure flag.
Validate the output the caller actually requested
A rule added to reject incomplete prose required a plausible sentence ending. It then rejected complete random text and a complete review ending in a machine-readable marker. One version accepted a fragment as complete; another rejected the full requested answer. Both came from treating a prose heuristic as a general definition of completion. The September 6 repair removes the prose-ending heuristic from acceptance decisions; message identity, page completion and all supplied output constraints still apply. An open-ended review has no known answer hash, so a minimum length remains only a way to reject obvious fragments. Reference U preserves the counterexamples.
The citation bug exposed a different distinction. The page displayed useful answers, but its interactive citation controls added newlines inside extracted JSON strings. Asking the model to produce better JSON would not fix our extraction. The repair removes recognized interface controls from a detached copy of the page structure and preserves the authored text. It does not repair genuinely invalid JSON. Reference J explains how the original returned text was checked.
The general lesson is to locate where correctness was lost: in the generated answer, in the page representation, in extraction, or in the caller’s validation rule. Each needs a different repair. Keep the original output so that cleaning up a test result cannot hide which layer failed.
A narrow patch can reveal a missing contract
The null-filename bug is a useful example. A no-PDF request sent JSON null; JavaScript’s default parameter handled only an omitted value. The attachment helper then threw. A test stub had removed that helper, while another stub hid the wrapper that lost structured error details. The user saw a missing-composer timeout even though the composer was visible.
Normalizing the nullish input was a small repair. Explaining the failure required following the actual argument through the helper, injection wrapper, readiness loop and saved observation. That chain is why diagnostics are part of the design: “still loading,” “inspection failed” and “not signed in” lead to different next actions. If every path becomes a timeout, the system encourages longer waits instead of a useful diagnosis.
Fixes need positive cases as well as prohibitions
Some trouble came from our own repairs. The stronger destination guard rejected the legitimate home → provisional → canonical sequence of a new chat. A shared wrapper’s improved error propagation bypassed another caller’s transient-state retry. Duplicate protection also blocked later authorized attempts whose durable records proved that nothing had been sent.
These cases needed explicit rules for permitted transitions, not weaker checks everywhere. The new-chat allowance stays tied to the original message; an existing canonical conversation cannot drift. A proven-zero retry keeps its operation key and failed parent, reserves a new run under the lock, and gets one new permit. Tests must cover both the legitimate transition and its closest unsafe neighbor.
Tests must retain the condition that caused the failure
The hidden-page regression was especially instructive: the ordinary browser harness had already enabled focus emulation, removing the condition seen in native Chrome. The null-input tests had a different blind spot but the same effect: replacing too much production code removed the cause before the test began.
The useful test is the smallest one that still contains the failure. That may be a small saved example of the page’s element tree—the Document Object Model (DOM)—or a test of the real injection wrapper, a restarted worker, native rendering or a real child process. For live acceptance, the record must also show that the repaired branch ran: a response with no citations cannot validate citation cleanup. More green tests help only when they cover the missing behavior.
Together, these failures changed the goal from making the page procedure work to making each decision explainable. The system must identify which conversation received the request, which answer belongs to it, what passed, and why another send may be unsafe.
Why the fixes kept multiplying
The audit on September 6 found that some safeguards still depended on the wrong kind of evidence. A timer could release a request whose delivery was unknown. A punctuation rule could reject a finished list. A selected widget named Max could be mistaken for the thinking control. These are design and scoping errors in our code, even though a changing website makes them easier to trigger.
The tests had another boundary problem. They could test the worker’s decisions while replacing the page inspector, or test an inspector while bypassing the wrapper that delivered its error. Both layers passed independently, yet their combination lost useful diagnostics. The repair adds an isolated test that carries real Python JSON through the real HTTP bridge, production worker and injected helpers into a browser fixture. It verifies the connection as well as the individual functions.
A borrowed tab also contains user-owned state. Replacing every synchronized draft made the old workflow convenient but could erase an unrelated unsent message. The current flow preserves that draft and reports the conflict. Similarly, explicit operation keys survive across days; the passage of time cannot establish that a missing acknowledgement meant no delivery.
These changes reduce the need for special cases. They do not eliminate unknown page layouts or make a later correct page prove an earlier failure’s cause. The new reference entries separate reproduced defects from such hypotheses.
Two September 7 incidents showed what was still missing. A caller reported a redirect from /c/<id> to a project route containing the same conversation ID; our guard rejected that route shape in reproduction. Another request reached ChatGPT, but three inline-code spans consumed six backticks in the rendered page. Our extractor lost those delimiters, so the full-prompt comparison could not confirm the send. Both were incomplete adapters between the page representation and our internal identity checks. Neither could be repaired by waiting longer.
Which hardcoded rules are actually harmful?
The useful distinction is what a rule claims to prove. A timeout that releases an unknown send, a punctuation test that defines completion, or a page-wide search for Max makes a weak observation decide correctness. Those shortcuts needed a different design. A selector for an observed inline-code element is also hardcoded, but it has a narrower job: interpret a known page representation, then pass the result through the original full-content check.
That adapter still needs maintenance. The repair must cover the legitimate case and its closest wrong neighbor: the same conversation ID on a project route passes; a different ID fails. Known single-backtick markup can reconstruct the prompt; changed code text or ambiguous markup cannot. A small string difference is evidence to investigate, not permission to accept an approximate match. AH and AI preserve those tests and the observed limits.
This is why counting reference entries would overstate the number of independent production bugs. The catalogue includes design defects, compatibility gaps, test defects, unresolved incidents and intentional refusals. A PDF outside an allowed directory is a valid refusal; leaving the caller unable to distinguish it from an uncertain send is a procedure gap. The durable lesson is to name the failed contract before choosing a fix.
Two later cases exposed the same issue at the caller boundary. An ordinary preference for more thinking became a strict refusal because the agent chose the wrong option. In another run, submission was confirmed before the page changed conversations; reading correctly stopped, but that generic failure did not enter automatic recovery. Neither case called for a weaker identity check. The missing distinctions were preference versus requirement, and delivery versus retrieval. AK and AL explain the separate repairs and remaining uncertainty.
A selected file can outlive the draft
On September 21, an empty composer was rejected twice as already containing an attachment. The production inspector found the reason: the native file input still held the previous PDF, although its message had been submitted and no attachment card remained. A separate browser inspection had shown an empty file list. We had treated two different observations as interchangeable, and treated a file selection as current application state.
The repair gives each observation a narrower meaning. The native FileList tells us what the file control retains. Current attachment cards and upload indicators tell us what the composer is preparing. Neither alone proves a successful send. We also locate the form directly from the composer when an empty input has no Send button. Tests now preserve the old native selection while checking empty, valid, wrong-file and uploading states. AM records the evidence and recovery boundary.
This is a concrete reason a system can accumulate bugs despite many passing tests: a protective rule can encode the wrong state model, and a test can erase the stale state that would expose it. The fix is to narrow what each signal proves and test its lifecycle. Adding another timeout or clearing the file input would conceal the distinction.
One page update can break several assumptions at once
On September 24, one text-only review exposed a chain of incompatible assumptions. The input no longer had the expected ID. After finding it, the code read an empty value property from an editable DIV that already contained the question. Its visible text also inserted layout spaces around URLs. The Send button had a new label, submission passed through an unfamiliar temporary URL, and the resulting conversation used new message containers. Each early refusal hid the next stage. AN–AR records the individual findings.
The website’s changing markup explains the compatibility work. It does not excuse our test design. Early fixtures paired the newly observed input with an old Send button or legacy message nodes. A passing round trip therefore proved a combination that did not exist on the observed page. The later fixture carries the new input, button, user message, assistant message and completion control together through the real bridge. Small fixtures must preserve the relationships that matter, even when their text is synthetic.
These failures fall into three useful categories:
| Kind of problem | Example | What needs to change |
|---|---|---|
| Wrong state model | Treating retained file selection as a draft, or an unconfirmed send as unsent | Define what each observation proves and which transitions it permits |
| Incomplete page adapter | Missing input labels, temporary routes or message containers | Interpret the observed structure in shared helpers while preserving identity checks |
| Missing test conditions | New input tested with old messages; background rendering already enabled by the harness | Preserve the whole relevant page state and test the production connection |
This is also how I judge a hardcoded rule. An exact selector with a narrow job and an ambiguity check is maintainable compatibility code. A weak clue that silently decides delivery or completion is a design shortcut. Adding labels can repair the first kind; adding timeouts or exceptions cannot repair the second. The aim is fewer unsupported assumptions, not fewer string constants.
The agent’s instructions must change with the code
The code can return a precise failure while the calling agent still follows an obsolete instruction. After the audit repairs, the skill contained both a broad “do not retry automatically” rule and a narrower rule allowing a continuation after maintenance proved zero submission. It also lacked guidance for the new busy result and retained old machine-specific paths in supporting files. Updating only the entrypoint had left the procedure inconsistent.
The revised skill gives the originating task responsibility for the requested answer and the existing maintenance task responsibility for the repair. They exchange the failed record and new evidence; the originating task then checks the repair and continues when the original request and submission evidence allow it. A busy listener calls for coordination with its owner. A real user draft needs to be preserved. An uncertain send stays blocked. These outcomes need different next actions, even though each initially looks like a failed tool call.
This keeps reusable browser decisions in code while teaching the agent how to interpret them. The skill is maintained alongside the implementation, but its installation is a separate check: an existing task can still hold an older cached instruction path. Reference AG records the changes and their validation limits.
The later inline-code incident also clarified the recovery handoff. Maintenance independently identified the original turn from its full prompt and retrieved its answer once, read-only; the originating task checked the review’s revision and quoted passages before using it. The original failed record stayed unchanged. The skill now makes that division explicit under the user’s standing coordination authorization: continue diagnosis together, give one task ownership of a recovery read, and close the incident when the requested feedback is actually usable. A generic failure before task creation needs a different branch, described in AJ.
The retained-file incident adds one more instruction: when the visible composer and a refusal disagree, maintenance gathers bounded evidence from the production inspector rather than using another send as a diagnostic. The originating task preserves the request and owns continuation after a verified repair. Both tasks keep investigating a repeated failure; neither starts a competing send or removes a real draft. Stopping sends and abandoning the user’s task are different decisions.
The September 24 procedure now asks the two tasks to record who owns inspection, code changes and the single recovery read. They check the remaining page stages together after a markup failure, and return repeated failures to the same investigation. A quiet page with Stop still visible may simply be generating: in this incident it finished without the proposed foreground activation. The skill must not turn a plausible explanation into an automatic workaround.
What remains outside the guarantee
There are still boundaries:
- Browser correlation is not a server receipt. The bridge observes page structure, not a transactional delivery API. Do not manually edit or send in the same composer while a task is active.
- Plain text is not lossless formatting. Interactive citation controls are omitted. Mathematical layout and arbitrary code formatting are not guaranteed to survive unchanged.
- Selected image questions are not universal OCR. The accepted PDFs include image-only evidence, but neither full-document transcription nor arbitrary scans were established.
- Authentication stays manual. Login, multifactor authentication, CAPTCHA and account recovery are not automated. Visible usage limits stop the workflow instead of provoking repeated sends.
- Local does not mean harmless. Requests, file paths, replies and private conversation links can appear in task records. They stay out of Git and published artifacts. Loopback binding, path allowlists and random pairing protect the local boundary, not against malicious software already able to read the user’s files.
The resulting contract is modest: one explicit request, at most one submission attempt, and either a checked answer or an honest failure state.
7. Debugging reference
The 45 entries, A–AS, group the documented failures and operational traps by the assumption that broke. Each explains the symptom, the repair or appropriate response, and its evidence. They cover page behavior, execution boundaries, worker lifetimes, installation, agent instructions and testing. Version labels identify dated repair milestones; this is not a count of 45 production bugs.
The entries distinguish observed defects, offline reproductions, preventive safeguards and unresolved incidents. In particular, Q remains unresolved; neither the later successful requests nor this catalogue establishes that every bridge bug is fixed.
Use this map to jump to the relevant boundary:
| Entries | Topic |
|---|---|
| A–C | Destination and message identity |
| D–F | Attachment readiness, displayed names and confirmation timing |
| G–L | Reply selection, rendering, citation text and cancellation |
| M–P | Installation, reloads, security and test hygiene |
| Q | Unresolved destination rejection and the later editorial review |
| R–V | Injection errors, worker restarts, deadline coordination, completion heuristics and mode controls |
| W | Evidence-first debugging and the boundaries around each workaround |
| X | PDF export, diagram spacing and misleading text comparisons |
| Y | Text-only input, swallowed inspection errors and testing the real helper |
| Z–AF | Paragraph extraction, route evidence, durable keys, drafts and the September 6 contract repairs |
| AG | Consistent caller instructions, ownership of repairs and bounded continuation |
| AH–AI | Project URL aliases, inline-code serialization and independent read-only recovery |
| AJ | Pre-task file validation, absent records and permitted staging |
| AK | Thinking-mode intent, diagnostic evidence and audited correction |
| AL | Post-submit destination changes and coordinated read recovery |
| AM | Retained native file selection, composer state and production diagnostics |
| AN–AO | New editor structure, exact text comparison and preserved unsent drafts |
| AP–AQ | Send-button ownership and another temporary conversation route |
| AR | New message identities, scoped completion and coherent page fixtures |
| AS | Generated preview bundles entering source checks during a shared release |
A. Provisional URLs are not yet canonical identities
Symptom. A new request reported submission_unconfirmed / conversation_mismatch while a message was visible.
Cause and repair. ChatGPT could transition through home → provisional /c/WEB:... → canonical /c/<id>. A guard expecting only a direct transition rejected the intermediate route. The repair accepts that bounded new-chat sequence while retaining the originating message, then pins the canonical identity. It does not let an existing canonical conversation drift to a different one.
Test lesson. Test a sequence of routes, not one final URL. Reuse an exact matching open tab; never fix a mismatch by selecting any active ChatGPT tab or allowing arbitrary redirects.
Design mistake. The earlier destination repair covered the invariant for an existing conversation but omitted a valid new-conversation transition. Its successful read-only check could not exercise that send-time route. This was a regression in our guard, not proof that ChatGPT changed its routing after the test.
The earlier destination check also had a timing hole: a tab could be correct when acquired and different by the time of submission. The guard now runs after navigation, before upload/input or mode changes, inside the submission expression and during response observation. The original redirect’s cause was not established. The repair closes the action boundary without pretending to explain why that page moved.
B. The whole user-message node is not the prompt
Symptom. The PDF and question appeared in Chrome, but prompt equality failed.
Cause and repair. The container also included the attachment filename, the PDF label and sometimes “Show more.” The extractor now selects one unambiguous prompt-body container: collapsible-user-message-content, with a unique older whitespace-pre-wrap fallback. Visually collapsed text must still be read completely.
Test lesson. Real-DOM fixtures include attachment cards, collapsed prompts, extra prefix/suffix text and ambiguous containers. Substring matching or globally deleting a filename would hide a mismatch rather than identify the correct field.
C. Mounted message counts do not identify a new turn
Symptom. Submission could not be confirmed after the page added or removed history.
Cause and repair. Virtualized history mounts only some messages in the page’s document tree, or DOM. A larger user-message count therefore does not prove a new submission. The repair snapshots known stable IDs and the last mounted ordering boundary, then observes new insertions. It retains MutationRecord.previousSibling evidence when React appends the new turn and removes the old boundary in the same batch.
A single witnessed candidate must also match the prompt, attachment and destination. An unseen ID alone is insufficient: older history can be mounted later. If ordering cannot be established, confirmation fails.
Test lesson. Include history inserted before the boundary, removal and insertion in one batch, remounting, an older identical prompt and multiple candidates. Counts remain diagnostics, not acceptance criteria.
D. Selecting a file is not proof that it is ready
Symptom. A file-input selection or a filename elsewhere on the page could look like a ready upload.
Cause and repair. Readiness must come from the expected attachment card inside the current Send form, outside the prompt. The inspector distinguishes selected, ready, uploading, failed, removed, missing and additional cards. Unknown markup blocks the send. After submission, the attachment must appear on the verified new user turn too.
Existing draft attachments are preserved and block a new send. Downloads may also lie outside configured PDF roots; staging an unchanged file into an allowed directory after a proven local rejection is different from retrying an ambiguous browser send.
Test lesson. Reduced observed card markup is placed in offline composer fixtures with busy/error/removal states. Those states exercise the contract; they are not a claim that every live upload UI was sampled. A history card or filename typed into prose cannot satisfy readiness.
E. The submitted turn may display a collision-suffixed PDF name
Symptom. The September 3 round 002 failed for the entire 120-second confirmation window, although the candidate matched the prompt and destination.
Proven cause. The PDF had been staged under its original name, but ChatGPT displayed a numeric collision suffix after submission. The exact-name-only attachment check rejected it. A sanitized example of the pattern is:
Before send: report.pdf
After send: report(1).pdf
Repair: 0.4.44. Only on a witnessed new user turn, permit an appended positive parenthesized integer before the unchanged extension. The full original stem—including any suffix it already had—must remain intact. The group label, action button and dedicated filename text must agree; the PDF label and every other turn guard still apply. Pre-send staging remains exact-name-only.
Evidence and limit. A reduced real-DOM regression failed before the patch and passed afterward. Later live runs observed (2).pdf, (3).pdf and (4).pdf. This is narrowly scoped UI correlation, not byte-level proof of the uploaded file. A different basename, inconsistent labels or renamed pre-send card still fails.
The ambiguous round-002 request was not resent. Later evidence did not retroactively turn it into a pass.
F. A short observation window can hide the real cause
Symptom. An earlier 77-page request reached ChatGPT, yet confirmation stopped after ten seconds with little useful evidence.
Repair. Observe the single send for up to 120 seconds, still bounded by the remaining preparation budget. Attachment staging and composer readiness have their own caps. After the agent acknowledges a confirmed user turn, start a fresh answer-wait budget. Polling also checks cancellation and saves bounded structural observations.
The composer can also appear late while the page hydrates its interactive controls. The worker now waits within the readiness cap before typing; a missing control is not immediately treated as a permanently broken page.
What was established. Five offline reproductions exposed four defects: the ten-second cutoff, count-dependent identity, whole-page attachment visibility and discarded observations. Later DOM inspection could not reconstruct which transient condition failed during that original ten-second window. The original attachment existed, so the staging false-positive was a separate defect—not a proven cause of that incident.
Test lesson. Preserve the last candidate, match results, ordering and timing evidence. A diagnostic candidateMessageId is not a confirmed submitted_message_id and must not be promoted merely to enable recovery.
G. The latest reply and an old Copy button are not enough
Symptom. An earlier review could be selected before the new PDF had an answer; a short prefix could look finished; unrelated busy UI could keep a finished answer waiting.
Two different counting mistakes were involved. Submission used mounted user counts (C); response selection sliced mounted assistant nodes using a pre-send count. React could reorder or replace those nodes, leaving the slice at a numbered fragment such as 1. This is why the repair needed a stable user-message anchor, not a larger assistant-count offset.
Repair. Bind extraction and recovery to the original submitted user-message ID. Scope completion controls to the associated assistant turn and combine them with generation state, stable candidate text and rejection rules. If the original anchor has been unmounted, fail instead of returning another visible answer.
Test lesson. Cover no-answer-yet, concurrent later questions, missing anchors, old Copy buttons, hidden busy indicators, citation-only content and long but incorrect strings. Minimum length is a rejection floor. Exact-copy constraints must not be weakened just to obtain a green result.
Design mistake. Identity, completion and output plausibility were being inferred from overlapping page-wide signals. They need separate checks: the right message can still be incomplete, and a complete-looking answer can belong to the wrong message. This explains why patching a prefix heuristic did not fix turn selection.
The busy-state bug worked in the opposite direction. A global querySelector('[aria-busy="true"]') could make a complete answer wait because an unrelated navigation or hidden component was busy. The repaired test uses a visible Stop control or a visible busy element within the current turn. Other busy nodes remain diagnostic counts, not vetoes. The old record did not preserve the offending element, so the exact transient widget cannot be named after the fact.
H. A focused window can still have a stalled page
Symptom. One live run returned only a five-character prefix after a 900-second wait and a further 600-second read. The native focus action succeeded, yet the document remained hidden and the current answer’s Copy action was absent.
The critical observation was not merely “GPT is slow”:
Window activation: succeeded
Document focus: true
Document visibility: hidden
Extracted answer: unchanged prefix
A later inspection found a long answer in the same conversation. It did not prove exactly when that answer finished or make the failed run successful.
A rendering lease is this project’s name for a bounded focus-emulation session on the verified target tab, using the Chrome DevTools Protocol (CDP). It changes page visibility behavior so browser-side updates can resume; it does not establish when model generation finished.
Repair: 0.4.39. A stalled operation can use one bounded native wake followed by that lease. Companion enables Emulation.setFocusEmulationEnabled, verifies that the document reports visible, and continues the same read loop. Chrome documents the visibility effect. Chrome rendering documentation, CDP method reference.
The lease neither reloads the page nor sends again. It is released on success, failure or cancellation. It does not detach another debugger. Chrome may show a debugging banner or bring the page forward.
Test lesson. This is an explicit browser workaround, not a general application API. Its useful properties are a verified target, observable effect and bounded lifetime. Completion checks still have to pass after rendering resumes.
I. The test harness had already supplied the workaround
Symptom. Ordinary browser tests passed while the native signed-in Chrome session stalled.
Cause. The installed Playwright implementation enabled focus emulation while initializing Chromium pages. That helpful setup removed the exact hidden-page condition the test needed to reproduce. A mock that only reported a successful native focus call hid the same distinction.
Repair. A regression launches a fresh, unsigned-in Chromium process over a native CDP pipe, without Playwright’s page initialization. Network requests are intercepted with local fixtures. A fixture queues an animation-frame callback to change Prefix into Complete review.
The test first proves the native background document is hidden and the callback has not run. It then exercises the production lease—even with a no-op native activation stub—and verifies both the update and restoration of hidden state after cleanup. Other cases cover debugger conflicts, enable failures, failed visibility checks and cleanup failures.
Limit. This reproduces a browser rendering mechanism, not ChatGPT’s entire application. Live tests were still necessary. The general lesson is to inspect what a harness changes before treating its passing result as production evidence.
J. Citation UI can make raw page text invalid JSON
Symptom. September 3 round 004 confirmed the renamed PDF and returned all eleven supported substantive answers. It still failed the requested strict-JSON contract.
Proven cause. Round 004 failed because inline file-citation controls contained block paragraphs. Chromium’s rendered innerText included their layout breaks, introducing literal newlines inside JSON strings. The response selector preferred that longer, UI-contaminated candidate over the cleaned one. This was contamination of extracted page text, not evidence that ChatGPT had authored malformed JSON.
Repair: 0.4.45. The extractor recognizes data-file-citation-group-identity, removes those controls from a detached clone, and serializes the remaining authored text with paragraph, list, table-cell and line-break boundaries. On that specific path, dirty raw UI text is no longer a competing candidate. Other markup retains the previous fallback.
This is not JSON repair: the live page is unchanged, literal filenames are not globally stripped, and genuinely invalid authored JSON stays invalid. The bridge reports fileCitationGroupCount and structuredFileCitationExtraction so a test can establish whether the repaired path actually ran.
Evidence. A reduced observed-DOM fixture ran red before the fix and green afterward. It covers multiple citation groups, literal code/filenames, block boundaries, invalid authored JSON and the existing identity/completion safeguards. The second 77-page acceptance run directly exercised eight citation groups; the first, citation-suppressed run did not.
The original format-failed reply remains failed. Correct facts plus a lenient parser are not an original strict-JSON pass.
K. Timeout, cancellation and polling are separate concerns
Symptom. A claim-timeout race could be mistaken for zero submission; another slow extension workflow could starve ChatGPT polling.
Repair. Claim and timeout decisions recheck state under the same lock. Cancellation before permit grant can prevent sending; after grant, delivery may be unknown. Cooperative cancellation crosses the Node/Python child-process boundary. Separate single-flight polling lanes prevent unrelated Companion work from monopolizing the loop.
Test lesson. Exercise both sides of the permit with a real child process, and overlap unrelated workflow waits. Stopping the wait does not unsend a message. An eligible extraction failure permits one message-bound read, not another send or an indefinite chain of read attempts.
Earlier versions used a 30-minute window for both duplicate fingerprints and visible-throttle cooldowns. The September 6 audit showed that this also released unresolved sends. In 0.4.54, explicit operation keys and unresolved sends remain protected regardless of age; only completed automatic content fingerprints retain the convenience window. Throttle cooldown remains separate. Reference AB gives the reproducer.
The duplicate history is stored on disk and survives process restarts. An explicit operation key identifies a request across that store; without one, the key is derived from the question, session or destination, attachment path, thinking mode and response constraints. It identifies a logical request, not the file’s bytes. Revising a PDF in place can therefore still match a recent request; a distinct revision filename makes the intended input clearer.
L. “Character” needs one cross-language definition
Symptom. Python and JavaScript disagreed on emoji; Windows line endings changed a raw hash.
Repair. The contract counts Unicode codepoints, not JavaScript UTF-16 code units. Both sides use the same normalization policy: NFC normalization, selected space and line-separator normalization, specified zero-width removal, line trimming and collapsed internal blank-line runs. Lines are joined with LF. Character count excludes LF; the expected-answer hash uses normalized UTF-8 bytes.
Prompt comparison and answer comparison have different jobs. The composer comparison collapses rich-text whitespace so equivalent wrapping does not fail exact prompt matching. Response normalization retains normalized line boundaries for the line-count and hash contract. Neither is permission to accept a prompt substring or rewrite substantive punctuation.
Test lesson. Include emoji and CRLF/CR/LF variants. Exact normalized equality is not preservation of raw typography, whitespace or PDF bytes. For open-ended prose, an extracted-text hash is not evidence that nothing was omitted.
M. A new plugin does not update every open task
Symptom. The new plugin validated, but another Codex task still referenced a deleted old cache directory or did not expose ask_chatgpt.
Cause and repair. Open tasks and in-memory servers could retain versioned paths after a reinstall emptied the cache. The launcher now points to the stable source repository. A helper restores only missing compatibility files in retained old caches; it neither overwrites intact files nor contacts ChatGPT. An old process may also need the missing Python adapter, not just a new launcher.
A fast Node entrypoint publishes the tool catalog before slow work starts. If a task’s initial catalog omitted the server entirely, repairing files cannot inject the absent tool into that catalog; a fresh task is still needed.
Test lesson. Check the installed route as well as source. Missing-cache repair is a zero-send maintenance action. Retrying a failed call is safe only when its submission state independently allows it.
N. Validation and reload need evidence, not optimism
Symptom. A skill validator failed because system Python lacked PyYAML or used the wrong Windows encoding. Separately, an extension source update was reported as if the running worker had already loaded it.
Repair. The project-owned validator runs with UTF-8 and isolated dependencies. Environment failure is reported separately from malformed skill content. The update wrapper validates, refreshes the plugin cachebuster, reinstalls and repairs missing compatibility entrypoints.
For the browser, the bridge checks manifest/protocol compatibility and permits a guarded reload when connected and safe. A disconnected extension cannot receive a reload instruction. Reloading must not restart an in-flight send.
Skill improvement. After an explicitly proven zero-submit disconnect, retain the exact unsent arguments. Ask for a manual reload once; when the user says “done,” resume that same request once under the documented safeguards. The user should not have to paste a recovery paragraph. The default thinking setting remains unchanged unless requested.
The duplicate guard also needed a repair: it blocked a later authorized attempt even when the saved terminal failure proved no submission. The bridge now permits that guarded retry with the same operation key and unchanged arguments. It checks the matching durable record and shared state together; missing, corrupt or conflicting evidence still blocks, as do in-flight requests, possible sends and active cooldowns. It does not start a retry by itself.
The replacement is reserved under the existing locks, links back through retry_of_run_id, preserves the failed record and receives its own single-use permit. The parent’s permit is not revived. An unclaimed disconnect also writes terminal state to disk so a restarted process does not see a stale queued task. This replaces the earlier suffix-key workaround; a new key or force_send is not a substitute for proof and authorization.
Design lesson. Duplicate protection must distinguish “the request may have been sent” from “a terminal attempt proved no send.” Treating both as the same recent fingerprint was safe against duplicates but incorrectly denied legitimate progress. The repair adds a proof-based transition; the age of a record or a user’s desire to retry cannot establish that proof.
Test lesson. Test missing, stale, incompatible and reconnected workers. Record the version that actually handled the live task. Neither successful installation nor the manifest on disk proves what an existing worker executed.
O. A local URL is not authentication
Problem. A predictable pairing token and acceptance of untrusted webpage origins would weaken the loopback boundary.
Repair. Use a random per-install pairing token, reject wrong tokens and foreign webpage origins, resolve PDF paths against configured allowed roots, and enforce file type and size limits. The pairing token is separate from ChatGPT authentication. A migration response for a disconnected worker exposes public reload/version metadata, not queued tasks.
Test lesson. Exercise real loopback HTTP with wrong tokens and origins. Never copy cookies, passwords, browser storage or Chrome profiles. This boundary is not designed to resist malware running with access to the user’s local files.
P. Tests and diagnostics can have their own bugs
Symptom. New citation diagnostics disappeared before reaching the saved Python record. Separately, saved source snapshots under output/ were linted as if they were active plugin code.
Repair. Extend the bounded observation allowlist to retain the new structural fields without admitting arbitrary page bodies or granting submission authority. Exclude output evidence from production lint scope, with a regression proving that real source and tests remain included.
Test lesson. A useful browser-side observation is worthless if an intermediate layer silently drops it. Moving a helper can also stale a source-string assertion; retain behavioral DOM tests rather than relying on where a particular line of code lives.
Most importantly, a tester’s PASS label is not the evidence. Keep the original reply, validate its requested format, check the PDF’s source facts and inspect the path that actually ran. A later successful read is useful evidence, but cannot rewrite an earlier ambiguous send as successful.
One health check had a surprising side effect. doctor --no-smoke skipped inference but still called an existing model loader, which loaded an idle pinned local model. No model configuration was changed. “No inference” was not “read-only”: later inventory checks must avoid the loader when loading is outside scope. This was a diagnostic lesson, not a ChatGPT repair or a model-runtime refactor.
Q. A later destination rejection is still unresolved
Observation. During an earlier editorial review on September 3, a single call targeted the saved blog-review conversation. The extension accepted the task and created a tab, but the destination guard returned conversation_mismatch before any submission attempt. The record explicitly set zero_submission_proven: true; no GPT feedback was retrieved.
What this does not prove. The saved diagnostics do not establish whether the conversation was unavailable, navigation had not settled, or a page/runtime behavior had changed. Those are possibilities to investigate, not competing diagnoses already demonstrated. The earlier two-scenario acceptance remains valid for those runs, but does not cover every old conversation.
Operational consequence. Preserve the failed record and ask for a confirmed destination or authorization to start a new conversation. Do not remove the destination guard, silently switch chats or call the unreviewed article GPT-reviewed. A future regression needs the actual observed route and page state at rejection; this record alone is insufficient.
What happened next. The user authorized a new conversation. That request confirmed one submission, but the caller’s execution context disappeared while it was waiting, leaving no final result and no active loopback listener. The saved conversation and confirmed user-message ID allowed one explicit, message-bound read. It retrieved a verified 20,236-character editorial review without another upload or send. This is a recovered result, not a clean first-pass completion, and it does not diagnose or repair the older destination rejection.
R. Page injection does not inherit the worker’s globals
Observed defect. An early live reply check returned no usable result even though the page had text. The injected inspectChatGptResponse function referenced ChatGptText, which existed in the extension’s service worker but not in the target page’s isolated execution environment. An injected function is a copied function body, not a closure carrying its caller’s globals. Chrome’s content-script documentation, checked September 3, 2026, explains this boundary.
There was a second bug in the wrapper: Chrome supplied a top-level InjectionResult.error, while execute() only read result. The real exception became an apparently empty result. Retrying that empty value turned a deterministic programming error into a long timeout.
Repair: 0.4.18. Install the text helper in the target tab before running the inspector, and propagate both injection-level and returned application errors. Retry genuinely transient empty observations, not missing code.
Debugging lesson. Inspect the complete injection envelope and test in the actual execution environment. A function that passes in a Node sandbox with injected globals can still fail in Chrome. This wrapper is shared: propagating errors also exposed a separate workflow whose success-page retry expected a returned error instead of an exception. That caller needed a narrow catch for its known transient missing controls, not a blanket suppression of injection failures.
The later readiness bug in Y exposed the other side of this contract: throwing immediately can discard diagnostic fields a caller needs to persist. A shared wrapper must define both how failures travel and which caller records their context. Testing only the page helper or only a mocked workflow leaves that agreement untested.
S. A finished page can outlive the extension worker
Observed defect. A page contained a complete editorial review, but the private run remained submitted and never received its completion callback. A new extension worker polled, found that the task was already claimed and received only idle.
The extension’s Manifest V3 service worker is its background event handler, not a permanent process. Chrome documents that it can terminate and lose global variables; a long-running JavaScript wait is not durable task state. This explains why restart-safe state is necessary, not the exact shutdown trigger in every incident. Chrome service-worker lifecycle, checked September 3, 2026.
Repair: 0.4.22. Record the claiming worker’s instance ID. When a different instance returns, a confirmed submitted task can be reclaimed only as a read of the same canonical conversation and message. The reclaimed command omits the prompt, file and send action. An unconfirmed ask cannot be reclaimed for another send; a task that was already read-only can safely resume reading. The original submission state remains intact.
Two small scheduling details mattered too. A poll arriving while the worker was busy was previously discarded; the scheduler now remembers pending work and runs it after the active operation exits. The claim window increased from 60 to 75 seconds so it does not race a full 60-second alarm cycle with no scheduling margin.
Test lesson. Simulate a new worker instance, not just another call from the same one. Assert the resumed command’s absence of send arguments. Separately test a queued poll and a slow unrelated workflow; restart recovery and independent polling lanes solve different problems.
T. Two deadline clocks can abandon the same live request
Observed defect. One PDF spent roughly three minutes preparing before submission. Python counted its 900 seconds from the start of the call, while Companion started another 15-minute wait after sending. Python closed the local listener first. Companion kept waiting, could not deliver /complete, and remained busy when a recovery read arrived.
Repair: 0.4.23, extended in 0.4.43. Pass the requested response window to both sides. Persist submission before starting the response phase, and leave a 30-second callback grace period on the Python side. Do not fix disagreement by giving every loop an unrelated larger timeout.
| Boundary | What its timer means |
|---|---|
| Worker claim | Up to 75 seconds, also capped by the request timeout |
| Composer / attachment / confirmation | Caps of 30 / 60 / 120 seconds, each within the remaining preparation budget |
| Answer generation and extraction | A fresh caller-supplied window after confirmed submission |
| Completion callback | Python keeps 30 seconds of grace beyond its phase deadline |
| Additional read-only recovery | At most 600 seconds for the one eligible recovery run |
Test lesson. Use short fake-clock windows to prove a reply can succeed when total elapsed time exceeds one window but post-submission time remains inside its own budget. Also test cancellation during preparation, confirmation and response waiting. A timeout describes a phase; it does not say whether the message was sent.
U. Completion heuristics caused false passes and false failures
Several repairs looked reasonable until the next test supplied a counterexample. The final rule is a combination of turn identity, completion controls, generation state and a declared output contract—not a stopwatch or one button.
| Tempting shortcut | Counterexample and what changed |
|---|---|
| Historical Copy buttons plus 20 seconds of stable text | The lighthouse sentence stopped at “three times”; a later 10,000-character test returned only 第000. Require the current answer’s Copy action and any supplied exact-copy constraints. Prefix shape alone cannot establish completion. |
| A voice/dictation button means generation ended | The control could remain available during generation. It is not sufficient by itself. |
| Any assistant node stable for three seconds is readable | A read-only path accepted a streaming prefix. It now requires the identified turn’s own completion control. |
| No completion evidence for 60 seconds means failure | A research-stage prefix could stay still that long. Continue the caller’s full response window while completion is absent. |
| A long answer must end in punctuation | Complete exact-copy output and a final marker such as BRIDGE-REVIEW-OK were falsely rejected. Those exceptions were intermediate repairs. Version 0.4.54 removes punctuation as an acceptance gate for both constrained and open-ended answers. |
| The longest candidate is the best answer | Repeated source-card titles and citation UI can be longer than the answer. Repetition checks are diagnostic hints, not an acceptance gate; the observed citation path uses structured extraction instead (J). |
The recorded changes span 0.4.20–0.4.28, with the later citation repair in 0.4.45. Earlier versions’ punctuation and voice-button rules were intermediate attempts, not the current guarantee.
The September 6 reproducer exposed one more inconsistency: a finished four-line list was rejected without a declared size, but accepted when the caller supplied a minimum of just one character. Both now use the same page-completion checks, without a punctuation veto. A minimum can reject a four-character prefix, but a long wrong or truncated answer can exceed it. Only known exact content and its normalized hash can establish an exact-copy result.
Test lesson. Keep both sides of every heuristic: the bad prefix it should reject and the legitimate short answer, random text or machine marker it must accept. Do not add a punctuation mark to the expected answer or lower its required length just to make a test pass.
V. Seeing “Max” is not proof that Max is selected
Guarded UI case. A visible popup can contain a Max option while another thinking mode is active. Finding that word anywhere on the page would confuse an available choice with the selected state.
The September 6 audit found that the selected-state search was still too broad: an unrelated selected widget named Max could pass. Version 0.4.54 requires a uniquely identified thinking control and scopes option state to its linked popup, then re-inspects after a requested selection. Its regressions distinguish an open popup option from a selected mode, and cover unavailable controls. Strict max stops before sending if selection cannot be verified; prefer_max can fall back and reports why. The ordinary current setting does not change the page’s mode.
The new counterexample is an offline reproduction, not evidence of an additional live Max-mode failure or a promise that every ChatGPT account exposes the same controls.
W. How we debugged without manufacturing a pass
The most useful habit was to reconstruct one request across boundaries before changing anything. “The page looks finished” and “the bridge returned a checked answer” are different observations.
First, classify the record. Check whether a send permit was granted, whether submission was acknowledged, which message ID was confirmed and whether a result callback arrived. Preserve the failed task and raw reply. Do not change an idempotency key, reload a sending tab or issue a parallel read just to get a more convenient outcome.
Then compare the two views. Read the saved bridge record and, when authorized, inspect the same conversation read-only. The useful browser evidence was small and structural: exact URL, ordered user/assistant IDs, prompt container, attachment labels, current-turn Copy, visibility/focus and text candidates. A screenshot helped establish visible state; it could not reconstruct a discarded observation from ten minutes earlier. Opening or focusing a page can itself change a rendering stall, so record the before-state when available and do not confuse a later inspection with a causal experiment.
Reduce the failure. Copy only the necessary, sanitized DOM hierarchy into an offline fixture and run the production helper against it. For state and timing, use deterministic clocks and controlled callbacks. For process or authentication behavior, use a real child process or real loopback HTTP. For native visibility, bypass the test harness that had already enabled focus emulation. Choose the lowest test layer that still contains the suspected cause.
Run the new test before the patch. The delayed-confirmation, collision-name and citation fixtures failed against the old implementation and passed after repair. That red-to-green sequence distinguishes a reproducer from a test written to applaud the new code. Conversely, the historical diagnosis script intentionally asserts broken behavior: its successful exit is evidence of a reproduced bug, not a post-fix regression pass.
Verify the route actually used. The first large-PDF pass after the filename fix displayed the exact original filename, so it did not exercise suffix handling. The first pass after the citation fix had zero citation groups, so it did not exercise citation removal. The later audits inspected observed filenames, citation counts and page metadata. A fixture name or prompt intention is not branch coverage.
Three mechanisms can all look like “recovery” in a casual summary:
| Mechanism | What it changes | What it must not do |
|---|---|---|
| Rendering wake and focus lease | Makes the verified page update during the same operation | Reload, type, upload, send or relax completion rules |
| Worker-restart continuation | Gives a new worker a read-only command for the same recorded run/message | Reconstruct an unconfirmed ask as a send |
| One additional read-only recovery | Starts a separate, bounded read after an eligible confirmed-send extraction failure | Select another answer or repeat the question |
The same discipline bounded the other workarounds. Filename normalization is post-send and structure-checked, citation cleanup operates on a detached clone, cache repair creates only missing compatibility files, and manual reload retry requires affirmative zero-submission proof. Each workaround has an observable trigger and a stopping rule; none is permission to ignore the guard that became inconvenient.
Finally, verify installation and then test through a fresh Codex task using the installed skill. Keep its instruction short so it cannot pass by following a controller-supplied repair recipe. The controller reopens the PDF and checks the original result independently. Section 8 gives the acceptance loop. Do not change the shared runtime while that test is active, combine passes from different revisions, or call unrelated failing tests green. Report targeted health, repository-wide health and live acceptance separately.
X. The PDF exporter and its checker also needed debugging
The review artifact introduced problems outside the bridge itself.
Diagram words joined together. Mermaid’s browser-rendered SVG used nested tspan elements. The PDF rasterization path did not preserve their spacing, so labels that looked correct in the webpage merged in the exported figure. The export helper flattens each plain-text label row while preserving its geometry and whitespace. It asserts that the row contains no meaningful bold/italic styling before flattening; this is a fix for these diagrams, not a universal SVG converter.
A text comparison mistook table ordering for missing prose. A spatial PDF extractor can interleave wrapped text from neighboring table cells. Comparing that result as one long string is a poor completeness check. The final check instead finds each rendered paragraph, heading, list item and table cell in content-stream extraction, while a separate spatial check looks for characters outside the page bounds. These tests establish block presence and bounds, not a perfect global reading order or visual fidelity.
An unsupported glyph erased the failure example. The English PDF fonts lacked the Chinese character in 第000. The first expanded export looked structurally complete but extracted that character as a NUL. The block-presence check caught the loss. A subset-embedded Chinese fallback font preserves the example, and explicit checks now require the original token and reject NUL characters. Keeping the PDF’s words intact is a prerequisite for testing someone else’s extraction.
The remaining QA was visual: render every page, inspect all contact sheets, enlarge dense diagrams and boundary pages, and keep short tables together. The reference starts on a new page; the private evidence list stays together rather than leaving one bullet on a final page. A text-layer pass alone cannot prove that an image is readable or that a PDF is pleasant to review.
For live test PDFs, the same principle applies in reverse: validate the generated source before asking GPT about it. Keep expected answers out of the question, recompute arithmetic from the actual PDF, and verify that an “image-only” witness really is absent from its text layer. A complex-looking PDF is not automatically a meaningful vision test.
Y. A null filename looked like a missing composer
Symptom. A text-only request timed out after about 30 seconds with “ChatGPT composer was not found.” Yet permitted inspection of the same tab showed an empty, visible, editable composer before and after the call. The failed task proved zero submission. Waiting longer would not repair the input that broke its inspector.
Reproduced cause. A request without a PDF still passed through the attachment helper, with attachment_filename: null. Its default argument covered an omitted value, not an explicit null:
function inspectName(expectedFilename = "") {
return expectedFilename.slice(0, 8);
}
inspectName(); // ""
inspectName(null); // TypeError
The test had replaced that helper with a stub, hiding the exception. Loading the production helper with the actual no-file argument reproduced it. This is an input-handling defect, not evidence that ChatGPT renamed the composer selector. The failed live record did not preserve Chrome’s native injection result, so we cannot reconstruct its exact exception envelope afterward.
Repair: 0.4.47. Normalize only a nullish filename to the existing empty value with expectedFilename = expectedFilename ?? "". Keep the actual-PDF and filename checks. A request that requires a PDF must still fail if its attachment is missing or wrong.
The execution wrapper had another diagnostic trap: it threw a returned { error, ...details } before readiness could save those details. Readiness now explicitly asks to retain that structured result long enough to record it and fail before typing. Native injection errors and other callers’ normal error behavior remain unchanged. A missing or invalid inspection result now reports invalid_composer_result, rather than waiting as though the page were loading. Genuine loading still uses the bounded readiness wait; cancellation still wins before input.
Test lesson. Run the real helper and the real wrapper around controlled inputs. The initial added regressions produced five failures; the expanded repaired set passed 25 cases, including omitted, null and empty filenames, required-but-missing PDFs, error propagation and valid void cleanup. Those checks explain why the fix works; the full-suite counts are recorded in Section 8.
Live evidence. After an idle rollout, a handshake established that the worker actually ran 0.4.47. A separately authorized retry kept the exact same question, session and operation key; the failed parent remained unchanged. The new run confirmed one submission and returned all 64 non-newline Unicode codepoints across four lines, matching the known expected SHA-256 without read recovery.
The owning task then sent its original roughly 34,000-character report and received a verified review containing 22,021 non-newline codepoints across 1,084 lines, also without read recovery or another send. That review’s hash was recomputed from the returned text; unlike the smoke test, there was no known expected answer to compare it against. Neither text-only case retested PDF upload, factual accuracy or the unresolved old-conversation rejection in Q.
The reusable lesson is to distinguish “the page is not ready” from “the code inspecting it failed.” Reproduce the actual arguments at the execution boundary before changing selectors or increasing timeouts.
This incident combined three gaps: an input case (null versus omitted), a diagnostic contract (returned details versus thrown errors), and test coverage (stubs replacing both boundaries). The input fix was small; the broader learning was to preserve those distinctions through the complete call. It did not justify loosening attachment validation or treating every empty result as transient.
Z. Paragraph boundaries are part of the prompt
Observed defect and repair: 0.4.48. A long text-only review failed exact prompt confirmation by three normalized spaces. The saved user-message DOM showed paragraph boundaries where textContent joined adjacent text. The intended prompt normalized to 26,996 characters; the old extraction produced 26,993.
The extractor now preserves paragraph, div and line-break boundaries while leaving adjacent inline spans and links joined. It reads the scoped prompt body, including collapsed prose, without accepting substrings or dropping the attachment checks. Saved-DOM replay and neighboring changed-text, truncation and historical-turn cases verify the correction. A later read recovered the original answer without resending the question.
Lesson. Normalizing whitespace cannot restore a separator that extraction already removed. Preserve the document’s meaningful boundaries before comparing text.
AA. Loading failures and route failures need different evidence
Reproduced race and repair: 0.4.50. The tab-load helper read the current tab before subscribing to completion events. Completion between those steps could be missed. It now subscribes first and rechecks the tab at the deadline. The race is reproducible; the original generic timeout lacked enough evidence to establish that it caused that particular incident.
Route evidence: 0.4.51–0.4.53. A separate diagnostic observation showed one requested conversation remaining at ChatGPT home. That does not distinguish account access, a deleted conversation or another external cause. A later incident reported only a generic destination mismatch and was initially described as transient SPA hydration. The September 6 audit corrected that claim: a correct page observed afterward does not reveal the failed page’s earlier URL.
The initial guard’s bounded home-only wait is therefore a narrow mitigation, not proof that the later generic mismatch was fixed. Version 0.4.54 carries structured destination evidence through response inspections too, so future failures can identify the actual boundary. No unresolved request was resent for this audit.
AB. An uncertain send must not expire
Offline defect and repair: 0.4.54. The old 30-minute deduplication window applied even to an explicit operation key and a request that had received a send permit but never confirmed delivery. Advancing the test clock by 31 minutes allowed the same key to queue another send without a resend override.
Explicit operation keys now remain durable. Unresolved sends—including automatic fingerprints—remain protected regardless of age. A later retry can proceed only with authoritative terminal zero-submission evidence, or a separately authorized resend override. Completed automatic content fingerprints retain a short convenience window. New records also bind explicit keys to their request arguments and reject conflicting reuse.
Lesson. Filtering accidental repeated clicks and protecting an uncertain external action require different retention rules. This test proves the old queueing defect; it did not send duplicate messages to ChatGPT.
AC. Reusing a tab must not erase its draft
Design risk and repair: 0.4.54. The bridge deliberately cleared synchronized prompt text, even when borrowing a tab already open for the user. It protected existing attachments but could replace unrelated unsent prose. The audit demonstrated the decision using a controlled draft; it did not establish a historical instance of user data loss.
The bridge now rejects a nonempty draft before staging a request and rechecks the composer at the input boundary. Input uses a collapsed caret instead of selecting the existing contents for replacement. The browser contract test preserves the original draft byte-for-byte and confirms zero submissions.
Lesson. Correct destination identity does not imply ownership of every editable field in that destination.
AD. Scope the thinking control before reading its selected value
Offline defect and repair: 0.4.54. The old helper searched all selected page widgets for Max, then scored candidate controls and tried their stored coordinates. An unrelated selected widget could pass the check.
The replacement requires one thinking control identified by its metadata and popup relationship. It reads options only inside that linked popup, takes fresh observations after interactions, and does not try a list of unrelated menus. Tests distinguish an unrelated selected Max widget, a correctly linked selected option and a missing popup relationship. prefer_max can preserve the current setting when controls are unsupported; strict max refuses to send without verification.
AE. Fix the error boundary for every caller
Reproduced defect and repair: 0.4.54. Earlier fixes preserved structured errors for composer readiness and the initial destination guard, but the response inspector used the same wrapper’s default throw path. It could still lose the observed URL and reason code.
The common execution boundary now carries those destination fields and the inspector phase when it throws. A connected test changes the fixture route after Python has acknowledged the submitted message. The production response path then fails with the changed route recorded and exactly one submission, rather than returning a generic string or retrying.
Lesson. An error protocol belongs to the shared boundary. Opting individual callers into special handling leaves the next caller exposed to the same defect.
AF. Test the connections and version the code that actually runs
Maintenance repair: 0.4.54. The new contract tests combine a real Python bridge on an ephemeral local port, production worker functions, actual injected helpers and a controlled browser page. Chrome extension APIs are adapted to that isolated browser; the page receives no live ChatGPT traffic. A real Python command includes a null optional filename, so the serialization condition behind Y remains in the test.
The scenarios check a completed unpunctuated answer, preservation of an existing draft, and a route failure after acknowledged submission. Separate DOM checks cover the linked thinking control. Existing cancellation, attachment and message-identity tests remain necessary; a connected text fixture does not exercise every PDF layout or native background state.
The audit also found that the plugin’s commits omitted much of the backend and extension source. The repair brings the relevant source and regression fixtures into a local Git checkpoint, with generated pairing values and runtime evidence excluded. A shared resolver replaces repeated personal absolute paths. Production idle version negotiation now supports installation checks without queueing a fake task; a matching version remains installation evidence, not live acceptance.
AG. A repaired backend can still have an outdated skill
Instruction inconsistency found after the September 6 repairs. The entrypoint prohibited automatic retries without stating the maintained zero-submission exception as clearly as its retry reference. Some failure branches still ended with reporting and stopping, even though the user’s standing instruction called for cooperation with the existing maintenance task. Supporting references also retained the old personal source path after the runtime gained configurable roots. These were observed documentation gaps, not a newly measured production failure rate.
The existing skill and its references now describe one procedure. The source task preserves the exact call, evidence and task identity, then contacts maintenance. Maintenance diagnoses and repairs without competing for the page, and sends its findings back. The source task checks those findings and remains responsible for obtaining the intended answer. A repeated failure stops further sends while diagnosis continues; a real draft or manual login is reported as a concrete dependency.
| Returned state | What the caller should do |
|---|---|
bridge_busy |
Preserve the rejected call. After the owner finishes and maintenance verifies availability, allow one continuation with the same arguments and key. There may be no task file because this invocation never queued; that says nothing about an earlier send with the same key. |
draft_conflict |
Preserve the draft. Investigate a false detection or keep the request pending until the composer is available. |
idempotency_conflict |
Reconcile the original arguments. A session identifies the conversation; an operation key identifies one question. A genuinely new authorized follow-up has its own key, while retries keep the old one. |
| Uncertain delivery | Continue diagnosis without another send. A new key, a source patch or elapsed time cannot establish zero submission. |
The update changes the existing skill rather than adding a competing recovery skill. Package 0.1.0+codex.20260906083757 carried the September 6 instruction changes; that release kept the browser implementation at Companion 0.4.54. The release procedure validates the skill metadata, checks its linked references and compares the installed files with the source. A fresh task is the supported boundary for loading the new package; older intact cache files are not silently overwritten. Those checks established instruction consistency and installation. The later two-task read recovery is recorded separately in AI.
Lesson. A new error code is incomplete work until the caller knows what it means, who owns the next action, and which evidence permits continuation.
AH. A project route can still identify the same conversation
Reported failure and reproduced defect, September 7. A caller supplied /c/<conversation-id> and reported a redirect to /g/g-p-<project-id>/c/<same-conversation-id>. Its record proved zero submission and a ready composer, but did not save the failed route or exact inspector. The destination helper rejected that supported project-conversation shape in a controlled reproduction. This establishes our missing route support; it does not establish why or exactly when the live page redirected.
Repair: 0.4.66. Recognize the complete project-conversation route and normalize it to the same /c/<conversation-id> identity. Apply the same grammar to Python input validation, tab reuse and the self-contained destination checks injected into the page. Project landing pages, arbitrary GPT routes and different conversation IDs remain rejected. The caller can preserve its original canonical URL and operation key.
Evidence. A connected fixture carries real Python JSON/HTTP through the production worker and injected helpers, redirects after composer readiness, and completes one synthetic send with the expected answer. Its different-ID neighbor refuses before input with the composer unchanged. These fixtures use no live ChatGPT traffic. The originating task subsequently reported that its one same-key continuation returned verified feedback; that report is distinct from the fixture and installation checks.
Lesson. A stricter destination guard needs a definition of equivalent identities. Broadly allowing project pages would lose the protection; rejecting every alternate spelling would reject legitimate work.
AI. Six missing backticks hid a successful send
Observed failure, September 7. Submission confirmation expected 25,157 normalized characters and extracted 25,151. The record showed one attempt, an empty composer and an unverified candidate. It did not prove zero submission, so another send was unsafe.
Read-only inspection independently located the intended user turn. Its prompt contained three code.user-message-inline-code elements: the page displayed the enclosed text but consumed each pair of single-backtick delimiters. The old serializer kept the children and discarded the six delimiters. Replaying that saved DOM reproduced the deficit; the repaired serializer reconstructed the complete prompt and matched the original normalized text exactly.
Repair: 0.4.67. Restore single-backtick delimiters only for that observed element representation. Keep the exact whole-prompt comparison. Generic code elements, changed code values, ambiguous prompt bodies and multiple-backtick spellings cannot pass by approximate matching. Ordinary inline spans and links still join correctly. This is a bounded page adapter, not a general inverse Markdown renderer.
Recovery evidence. Before requesting a read, maintenance verified the full prompt independently of the failed candidate ID, established the actual user-message identity and following assistant turn, and checked that no newer user turn intervened. One read bound to that identity returned verified text: 12,017 non-newline characters across 315 lines. The originating task checked the saved response hash, intended draft revision and two quoted passages before using the feedback. No resend, upload or page reload occurred.
The original failed record remained unchanged. The separate read used the preceding runtime, so it proves recovery of that answer, not fresh send acceptance for the new serializer. Synthetic positive and negative DOM cases and a connected Python/worker fixture cover the repaired branch offline.
Lesson. A tiny character deficit can invalidate identity without implying lost substantive content. Establish which representation changed, compare the complete input, and preserve the distinction between an unconfirmed send and a later verified read.
AJ. A PDF policy rejection can happen before a task exists
Operational refusal, inspected September 9. Two byte-identical copies of an authorized review PDF were supplied from a checkout and a worktree outside the configured upload roots. Both calls returned an adapter-level PermissionError and no task file. Moving between arbitrary working directories could not satisfy the configured policy.
Source inspection established that this precise path check throws before request fingerprinting, shared-state reservation and task creation. The local HTTP listener may already have started, but this invocation has no browser task to claim. Read-only inspection found neither the original operation key nor its session in shared state, and no matching task record. The missing file alone, a short call duration or the generic error name would not have established those facts.
Procedure correction. Check PDF policy before the first call. For this verified no-record case, the source can stage the same approved PDF as a regular file in an existing allowed directory, check resolved containment and byte identity, and change only the attachment path while retaining the question, session, operation key and output/model constraints. Continuation remains bounded to one current, authorized request. An earlier record for the key requires reconciliation because the path contributes to the request fingerprint. Do not widen the allowlist, use a link alias or change keys to evade that check.
This was an intentional input boundary, not a ChatGPT delivery defect. The skill update fixes the missing caller procedure and keeps diagnosis with the existing maintenance task. No upload or successful review is claimed for this inspection.
AK. A preference must not become an invented requirement
Caller error and unsupported control, investigated September 9. The user wanted more thinking, but the caller imposed strict max: refuse to send unless Max can be verified. The page exposed a closed Medium pill and a slider whose highest visible setting was Pro. The existing helper did not recognize that representation. An internal data-max marker described a slider endpoint; it did not prove that Pro meant Max. The request stopped before upload or prompt entry.
Diagnostic repair: 0.4.68. Preserve the unsupported-control reason and any attempted selection through the worker, HTTP result and saved record. The connected regression found a second boundary dropping these fields in Python, so both sides needed the bounded diagnostic schema. Controlled cases cover the unsupported pill, the Pro slider and a supported linked Max option. The live inspection restored the original Medium setting and submitted nothing.
Instruction and state repair. An ordinary Max preference maps to prefer_max, which can preserve the current setting and report the fallback honestly. A real user requirement remains strict. Correcting an accidental strict choice under an existing operation key needs more than editing arguments: the key is bound to those arguments. The maintenance command therefore requires a current failed run with authoritative zero submission, changes only the mistaken mode, records an audit and leaves the original failure intact. It queues nothing itself. A submitted or uncertain request cannot use that correction.
The correction was tested with 14 state cases and four connected thinking cases. It was not applied to the production request because the source had superseded its PDF. Those checks establish the correction boundary, not a completed live review. The changes are recorded in backend commits 56bce09 and f2fe6b5, and plugin commits ca57e39 and 570a537.
Lesson. A safety option becomes a workflow bug when the agent invents a stricter requirement than the user gave. Preserve intent, carry enough diagnostic evidence to explain a refusal, and reconcile durable state before continuing.
AL. A confirmed send can still fail during reading
Observed incident, September 11; navigation cause unresolved. A review request was confirmed with a durable user-message ID. Later, the response inspector observed a different conversation URL and returned conversation_mismatch. Its destination check runs before inspecting message bodies, so it refused the unrelated page. The saved events establish the identity change but do not identify what caused it. An earlier rendering wake is recorded; its presence does not prove it caused the navigation.
Recovery gap. The backend automatically reads once for response_unverified or timeout_after_submission. This incident returned failed, so automatic_read_recovery_attempted was false even though the send had succeeded. The skill explained older results without that field and missing message IDs, but did not clearly cover this combination. Treating every failure as an unconfirmed send would give the caller the wrong next action.
Procedure repair. Maintenance checks the original destination, confirmed message ID and absence of an earlier recovery, then assigns one task a single read bound to that URL and ID. The read selects the exact destination and retains the normal identity and output checks. The source checks the recovered review’s revision before using it. The original failed record stays unchanged; a failed read returns to diagnosis without another read loop. The unrelated conversation’s body is outside the investigation.
This repair clarifies the existing skill; it does not relax the URL guard or add a resend. The source-owned read subsequently returned verified extraction of 9,754 characters, and the source confirmed that the feedback matched the nine intended article revisions. Maintenance did not open ChatGPT or perform a competing read. Retrieval is resolved; the cause of the intervening navigation remains unknown.
Lesson. A single success/failure label cannot describe a multistep external operation. Delivery, extraction and recovery need separate evidence, so a correct refusal still leaves a useful path to the requested result.
AM. A native file selection can outlive its message
Observed defect, September 21. Two attempts to review a new PDF stopped before upload with an existing-attachment refusal. A previous PDF review in the same conversation had completed. The composer had no text or attachment cards, but the native file input retained the previous PDF. Both failed records proved zero submission. A separate browser inspection had not exposed that native selection, so its empty view could not explain the production refusal.
Root cause. The preflight rejected any nonempty native FileList, treating it as a pending draft. The attachment helper also treated an old selected filename as an unexpected current attachment. Those assumptions confuse the file control’s retained selection with the application’s current attachment state. An empty composer without a Send button exposed another gap: the inspector had relied on that button to find its form.
Repair: Companion 0.4.71. Use current scoped cards, exact names and readiness for attachment acceptance. Locate the form from the composer when necessary. Keep raw selection as diagnostic evidence; it cannot prove readiness or invalidate a verified current card by itself. Existing text, real cards and uploads in progress still block preflight. The repair does not clear user state or loosen the final submitted-turn check.
Evidence. A separate read-only production diagnostic selected the same existing tab and reported zero characters, zero cards and one old native selection. After repair, it found the actual composer form and correctly reported the new PDF as missing: no upload had happened. It queued no sends or response reads and left the failed record unchanged. Offline Chromium tests preserve a real FileList with no Send button and cover empty, correct-card, wrong-card and uploading states. Worker tests check both preserved drafts and continuation with a retained selection.
Recovery boundary. The source task owns one continuation with its preserved arguments and operation key after verifying that the request is still current and the repaired runtime is active. Production diagnosis and passing fixtures do not establish that the requested live review has finished. A repeated failure returns to joint investigation without a send loop.
Lesson. A lower-level control can retain state after the application has moved on. Measure the state that the decision actually concerns, preserve contradictory observations, and test transitions between requests as well as a fresh page.
AN. The composer existed without its old ID
Observed defect, September 24. A text-only request timed out before input. The page had a unique visible form-contained editable DIV labeled Ask ChatGPT, but no legacy ID or testid. The three old selectors could not match it. The earlier hidden-page observation did not establish a rendering cause.
Repair: 0.4.72. A shared resolver recognizes the observed accessible textbox when unique, visible and outside dialogs or history. Readiness, native input, mode-control scoping and submission use that same resolver. The native-input execution world receives the actual self-contained helper. Wrong-label, hidden, duplicate and unrelated controls remain ineligible.
Lesson. Fix the shared page contract, then exercise the subsequent stages. Finding a textbox is not proof that its text, submit control or resulting message will be interpreted correctly.
AO. Input text had three different representations
Observed defect. The continuation found the editor, but full-question comparison failed before a send permit. Its DIV exposed an empty value while containing text. Reading visible innerText produced 733 normalized characters versus the expected 730; raw textContent had 724. A complete comparison located exactly three extra spaces before inline URLs. An older zero-character diagnostic was from before input and did not show failed insertion.
Repair: 0.4.73. Read value only from native text controls. For rich text, serialize text nodes, paragraph and line-break boundaries, and uninterrupted inline children. Record a separate post-input comparison. Preserve the full expected question; do not erase spaces globally or accept a similar string.
Continuation. The original text was already staged. The backend can allow the same proven-unsent operation to retain that exact draft on its recorded tab, skip insertion, and repeat all pre-submit checks. It preserves the failed parent. Changed text, attachments, another tab, missing proof or an uncertain send receive no exception. Connected tests check zero native insertions during this continuation and refusal of an edited draft.
Lesson. Text in the application, text exposed by layout and text in a form-control property are different representations. Reconstruct authored text before comparing it; save diagnostics from the stage that actually failed.
AP. The Send control changed too
Observed defect. With the original draft preserved, comparison passed at 730/730 characters. The request then stopped before a permit because the old selectors missed an enabled submit button labeled Send, with no legacy testid.
Repair: 0.4.74. Readiness and native submission share a resolver that requires one visible button owned by the composer’s form. The observed label is supported alongside legacy labels; disabled, non-submit and ambiguous controls fail readiness. A proven-unsent Send-not-ready parent can retain its draft only when the saved post-input check verified the entire question.
Lesson. A test that combines a new editor with an old Send button misses this failure. Preserve the observed pair and test actual native form submission, including wrong-form neighbors.
AQ. Another temporary URL interrupted confirmation
Observed defect. The next attempt reached native submission. Confirmation stopped 561 ms later at /c/local-chatgpt%3A<uuid>, an unrecognized temporary route. The original tab later reached a stable canonical URL without navigation or reload. Submission was attempted; the failed confirmation did not prove zero delivery.
Repair: 0.4.75. Recognize the observed UUID route and encoded colon as provisional. Keep the allowance limited to the new-chat transition; existing canonical conversations cannot drift. Provisional observations may be recorded, but session tracking and successful completion require the stable canonical identity. Response reading must still establish the original user message.
Lesson. Route recognition and delivery evidence are separate. Repairing the route never unlocks another send for this operation. The original failed record remains unchanged, and any permitted recovery reads the existing answer once, bound to independently verified identity.
AR. A shared turn container does not identify the answer
Observed defect. The new page had no legacy message-role nodes. It exposed separate user and assistant search units, but both sat inside an outer turn whose key was the user’s message ID. The assistant’s own attribute repeated the same assistant UUID twice. The final Copy button was outside the assistant unit; the user’s Copy message and the assistant’s Copy table controls served different purposes.
Repair: 0.4.76. Shared message helpers serve both submission confirmation and response reading. Repeated identical UUIDs identify one message; conflicting IDs, ambiguous units and wrong ordering are rejected. The new assistant must belong to an exchange with the uniquely identified original user. Extraction reads only its unit and removes interface controls from a detached copy. Completion requires the associated visible response Copy action; user, table, hidden, preceding and foreign Copy buttons do not qualify. Stop and current-exchange busy state still indicate generation.
Evidence and limit. A reduced fixture tests the observed message structure, original prompt comparison, temporary-to-canonical route, duplicate IDs and units, newer users, and misleading controls. A connected fixture also exercises the new composer, Send control and messages through the real Python HTTP bridge and native submission. The actual worker was observed idle at the repaired version with no queued task. The source task then completed its one message-bound recovery read: 10,907 non-newline characters across 746 lines, with verified extraction, one associated Copy action and no Stop control. The original send was not repeated or relabeled as confirmed. ChatGPT reported reviewing all 35 current public article openings and structures, with 11 close readings, but could not open the requested fixed deployment. This resolves retrieval; it does not establish a complete review of all 35 articles at that fixed revision.
Subsequent live acceptance. A genuinely new authorized question then completed in the same conversation: submission was confirmed and 4,590 non-newline characters across 213 lines were extracted without automatic recovery. The caller checked feedback about seven revised article structures. This exercises normal text input, submission confirmation and reading on the repaired runtime; PDF upload and full-article factual accuracy remain outside this new check.
An unproven hypothesis. Structural counts remained unchanged for several minutes with Stop visible, but generation finished before any proposed foreground activation. This incident does not demonstrate a rendering stall or a successful focus workaround. Intermediate unowned text and the user’s Copy control were correctly insufficient to accept an answer.
Lesson. Preserve role, message identity and completion ownership separately. A container’s key is not a substitute for its children’s identities, and a familiar button label is meaningful only in the right scope.
AS. A local preview became type-check input
Observed maintenance mistake, September 24. To keep an AskGPT revision local while another task published different articles, I built its preview under tmp/. The first type check passed before that directory existed. A later public build included the generated JavaScript in its checks and exhausted the Node heap. This was a failure introduced by the preview arrangement, not a ChatGPT bridge defect.
Cause. The project extended Astro’s strict configuration, whose inherited source glob included **/* with JavaScript enabled and only dist excluded. Git ignoring tmp did not exclude it from type checking. A directory can be safe from Git and still be input to another tool.
Repair and evidence. The publishing task retained strict checking and explicitly excluded dist, node_modules and tmp, without increasing the heap limit. The later check passed with the preview bundles still present: zero errors and zero warnings. The existing blog-preview skill now requires checking the effective inherited configuration and repeating validation after custom build artifacts exist. The shared release also preserved the two local-only articles, built their older public versions, and restored the local revisions after deployment.
Lesson. Development tools share a workspace too. Validate the state left by a build, not only a clean checkout, and treat preview output and public release input as distinct responsibilities.
8. Test method and source notes
The independent-task repair loop
The project’s README calls this the 10-minute repair loop. It separates the tester from the controller.
“Independent task” means a fresh execution and instruction context, not automatically independent ground truth. The tester may generate both a fixture and an answer key. The controller must therefore reopen the source PDF and recompute the relevant facts, including visually checking image-only evidence, rather than trusting that key.
The cycle is:
- A fresh Codex task reads the installed skill, creates or receives a complex PDF, and attempts the authorized workflow. Its prompt stays short so it tests the skill, not a pasted workaround.
- It saves the PDF, expected answers, original bridge output and a report in a readable test folder.
- On the next scheduled wake, the controller checks that evidence against the source. It does not start another test while one is active.
- A failure leads to a small reproducer, a scoped repair, offline validation and installation. The failed result stays failed.
- Fresh live tests run on the repaired runtime. Acceptance requires a new-conversation pass followed by a different-PDF pass in the same conversation, without changing the relevant runtime or skill between them.
Ten minutes is the controller’s check interval, not a ten-minute send cadence. An active test may take longer. Ambiguous sends are not retried, and runtime updates do not interrupt an in-flight request.
The September 3 loop stopped and its heartbeat was deleted after the two accepted scenarios. It is not a permanent background sender or a promise to keep testing after completion.
Historical results, not newly repeated claims
| Earlier case | What it established | Important boundary |
|---|---|---|
| One-page token PDF and text prompt | Feasibility of the round trip | Not long-answer reliability |
| 10,000-character PDF and direct-text cases | Exact normalized copy checks; some prefixes rejected and recovered read-only | Not every path passed on its first extraction |
| Seven-page blog PDF | An 11,330-character editorial report | A review, not exact transcription |
| Thirty-page PDF | Four tokens retrieved from pages 1, 10, 20 and 30 | Not all-page coverage |
| Five-page Unicode PDF | 10,000-codepoint content checked in a later message-bound read | Original submission-confirmation run failed |
| Two eight-page PDFs, September 2 | 7,213 and 9,329 characters; 29/29 and 16/16 checks on 0.4.39 | Different checklists; earlier repair snapshot |
| Seventy-seven-page sequence, September 3 | Exposed filename and citation defects, then passed the two scenarios in Section 5 | Earlier failures remain in the record |
The earlier PDF repair checkpoint recorded 444 offline tests passed: 268 Python, 94 Companion JavaScript and 82 plugin tests. Six unrelated live/LM Studio integration tests were excluded. It used Companion 0.4.45 and plugin 0.1.0+codex.20260903101054; those labels describe different components, not interchangeable versions.
The later composer repair in Y recorded 487 offline passes: 296 Python, 109 Companion JavaScript and 82 plugin tests, with six live/LM Studio cases excluded. Ruff, ESLint and the isolated skill/plugin validators passed. The installed package was 0.1.0+codex.20260903203321, and the actual live tasks recorded Companion 0.4.47. The new checks exercised the real helper/wrapper and durable retry state; the two live text-only runs checked the installed route. Neither replaces the earlier PDF acceptance cases.
These are saved repair-cycle results, not a claim that editing this article reran the bridge suites. Targeted tests overlap the full suites and must not be added again. More passing tests across changing suites is not itself a reliability score. Health checks also have side effects to account for, as P describes.
September 6 audit repairs
The audit repair reran 539 offline checks: 315 Python, 129 Companion JavaScript and 95 plugin tests, with six live/LM Studio cases excluded. Ruff and ESLint passed. The connected browser fixtures exercised the actual Python HTTP bridge and injected helpers without ChatGPT traffic. Overlapping local calls now receive a structured busy result before queueing, rather than a raw port-binding exception; this is an explicit refusal, not a shared request queue.
The installed plugin was 0.1.0+codex.20260906082257. A task-free production handshake observed Companion 0.4.53, requested its normal reload, then observed 0.4.54 idle. No new live ChatGPT send, recovery or PDF acceptance was performed in this repair. The September 3 results remain historical; the new checks establish the specific contracts in AB–AF.
September 7 representation repairs
The project-route report records 343 Python + 149 Companion JavaScript + 97 plugin checks. The subsequent inline-code repair records 343 + 149 + 107 = 599 offline checks, with six live/LM Studio cases excluded. These are successive suite snapshots, not additive experiments or a measured success rate. Ruff, ESLint and skill/plugin validation passed in those repair cycles.
The route change is recorded in WebContentAgent commit 0fbad81 and plugin commit 7caf794; the inline-code change is in b53aec2 and 0e3e054. The later snapshot used Companion 0.4.67 and plugin 0.1.0+codex.20260908063720. A task-free production handshake checked the installed worker without sending or reading. The saved-DOM replay established the six-delimiter cause; the connected fixture tested the repaired send-confirmation path; the live read recovered one existing answer. Those evidence roles cannot substitute for one another.
The September 9 skill update, installed as 0.1.0+codex.20260909085405, documents the recovery ownership and pre-task validation branches. Skill/package validation and a byte comparison of the four edited instruction files passed. The article update preserves these dated evidence boundaries. It does not rerun these historical live requests or turn them into fresh acceptance results.
September 21 retained-selection repair
The repair passed 375 Python, 158 Companion JavaScript and 114 plugin tests, with six live/LM Studio cases excluded; Ruff and ESLint passed. These are full-suite snapshots, not 647 new tests or a success-rate estimate. The production diagnostic observed Companion 0.4.71 and exercised the existing target without submitting, uploading or retrieving a response. It verifies deployment and the observed composer state, not completion of the originating review.
The existing AskGPT skill now explains the production diagnostic, retained selection versus current draft, and source-owned continuation after repair. Its coordination rule keeps both tasks working on the incident while preserving one owner for sends and recovery reads. Updating the instruction source and installing it are separate steps; old tasks may still hold a cached copy.
September 24 page-contract repairs
The final adapter snapshot passed 387 Python, 163 Companion JavaScript and 152 plugin tests, with six live/LM Studio cases excluded; Ruff and ESLint passed. The tests use synthetic messages and isolated browser pages. A task-free handshake observed Companion 0.4.76 idle and queued no sends or reads; it did not observe a reload sequence. These checks establish the repaired page contract and active worker version. The two live cases in AR establish different paths: recovery of the existing answer, then a new text request that completed normally without recovery. Neither repeats PDF-upload acceptance or removes the stated review-coverage limits.
The repair sequence is recorded in WebContentAgent commits d1bbad5, 2692380, 47bcc01, 7c8d9e0 and 9ad3f60. Plugin commit df48533 adds the complete new-message fixture and coordinated recovery instructions. The existing skill was reinstalled as 0.1.0+codex.20260925024520; validation passed and all installed skill files matched their source. No new skill or background sender was created.
Public mechanism references
Public browser references explain the focus-emulation mechanism: Chrome’s rendering guide and the CDP method definition, checked September 3, 2026. They do not prove the cause of every historical ChatGPT incident.
The content-script injection guide and service-worker lifecycle guide, also checked September 3, explain the execution and lifetime boundaries in R and S. Incident-specific causes come from the saved local reproductions, not from those general platform documents.
Private implementation evidence
The catalogue was checked against Git history, current source, the saved problem journal, repair reports, incident records and regression code. Git alone was incomplete: acceptance artifacts had been committed while much of the implementation was still uncommitted. Repair reports and observed worker versions establish the dated implementation evidence; commit order is not deployment order. The earlier September 3 private source map contains 72 evidence rows, including safeguards and limitations, rather than 72 distinct production bugs; it predates the additional Z–AL entries.
The following paths identify local documents, fixtures and regression tests used for this write-up. They are not public sources or reader-accessible downloads:
-
WebContentAgent/docs/chatgpt-pdf-rendering-stall.md: hidden-page investigation. -
WebContentAgent/docs/chatgpt-submission-confirmation.md: timing, identity, upload readiness and filename rules. -
WebContentAgent/docs/problem-journal.md, ChatGPT entries 30, 35–39, 43–45 and the September 3 incident notes: earlier injection, lifecycle, completion and recovery failures. -
WebContentAgent/tests/unit/test_chatgpt_chrome_bridge.py,test_chatgpt_review_regressions.pyandtests/js/chatgpt-*.test.mjs: state, clocks, cancellation, mode controls and response selection. -
codex-chatgpt-bridge/tests/chatgpt-render-lease.test.mjs: native rendering regression. -
codex-chatgpt-bridge/tests/attachment-collision-dom.test.mjsandtests/response-citation-dom.test.mjs: reduced real-DOM regressions. -
codex-chatgpt-bridge/docs/regression-tests.mdand the README: offline checks, installation and acceptance procedure. -
output/continuous-pdf-tests/submission-confirmation-2026-09-03/in the bridge repository: original reports, repair notes and controller audits. -
output/diagnostics/submission-confirmation-2026-09-03/in the bridge repository: pre-fix reproductions, retained uncertainty and the red-to-green repair report. -
output/diagnostics/composer-readiness-2026-09-03/in the bridge repository: preserved null-filename failure, actual worker negotiation, exact-copy acceptance and the separately owned long-report result. -
WebContentAgent/tests/js/chatgpt-composer-readiness.test.mjs,chatgpt-execution-boundary.test.mjsandtests/unit/test_chatgpt_zero_submission_retry.py: actual helper arguments, wrapper errors and guarded same-key retries. -
WebContentAgent/docs/askgpt-code-audit-2026-09-06.mdand the September 6 repair report: reproduced audit findings, corrected causal claims and repair status. -
WebContentAgent/docs/askgpt-project-route-repair-2026-09-07.mdanddocs/askgpt-inline-code-repair-2026-09-07.md: the observed/reproduced causes, separate read recovery and dated validation behind AH–AI. -
codex-chatgpt-bridge/tests/user-inline-code-dom.test.mjs: exact prompt serialization and negative cases for changed or unsupported markup. -
WebContentAgent/docs/askgpt-max-slider-diagnosis-2026-09-09.md,docs/askgpt-mode-preference-correction-2026-09-09.mdandtests/unit/test_chatgpt_mode_correction.py: the separate control, intent and durable-state findings in AK. -
codex-chatgpt-bridge/docs/askgpt-post-submit-route-recovery-2026-09-11.md: structural incident evidence and the source-owned recovery handoff in AL. -
WebContentAgent/src/web_content_agent/chatgpt_chrome_bridge.py, attachment validation and task reservation: the pre-task ordering checked in AJ, alongside a private read-only state inspection. -
codex-chatgpt-bridge/tests/audit-boundaries-dom.test.mjs: real Python HTTP, worker and injected-helper contract tests with isolated browser fixtures. -
codex-chatgpt-bridge/skills/chatgpt-pdf-review/SKILL.mdand its failure-coordination, composer-readiness and reconnect references: caller behavior after the audit repairs, including the distinction between stopping sends and continuing diagnosis. -
The blog’s local PDF export helpers and source-to-reference coverage audit: artifact-specific fixes and the documentation completeness check, not bridge runtime tests.
-
WebContentAgent/docs/askgpt-retained-file-selection-repair-2026-09-21.md,scripts/inspect-chatgpt-composer.pyand the composer diagnostic tests: production evidence and bounded read-only diagnosis in AM. -
codex-chatgpt-bridge/tests/submission-confirmation-dom.test.mjs: native FileList, missing Send control and current attachment regressions. -
WebContentAgent/docs/askgpt-accessible-composer-repair-2026-09-24.md: the observed page changes, repairs and evidence boundaries in AN–AR. -
codex-chatgpt-bridge/tests/search-unit-dom.test.mjsandtests/audit-boundaries-dom.test.mjs: message ownership, scoped Copy controls and the connected new-page regression.
Private conversation IDs, full raw task records and credentials are not published with this article. Ordinary regression fixtures do not send messages; live ChatGPT tests require separate authorization.
Two passes close a defined repair cycle. They do not make a changing browser interface permanently reliable. The following Chinese retrospective explains the recent work and turns the remaining gaps into a concrete plan.
9. 这轮调试是怎样推进的
这一节写于 9 月 25 日,复盘的是前面记录的 9 月 21–24 日排障,不代表当天又发生了一组新故障。AskGPT 的任务很简单:把一个获准的问题送到指定的 ChatGPT 会话,再把属于这个问题的完整回复带回来。难点在于,程序只能观察网页,不能因为工具报错就知道问题没有送达。
这轮反复主要来自三件事叠加:页面结构变化、我们对页面状态的错误理解,以及没有保留真实页面组合的测试。 因此,维护工作既包括修网页识别,也包括检查“凭什么允许下一步”。新增一个选择器只解决了前者。
先看失败停在哪一步
提交给 ChatGPT 的问题有副作用:发出去后,即使本地程序没收到确认,对方也可能已经开始回答。因此,我首先检查保存的操作记录,把“没有拿到结果”拆成几个不同阶段:还没输入、已经输入但没获发送许可、已经尝试发送但没确认、已经确认但读取失败。每一类的下一步都不同。
这次有两个配合的 Codex 任务。发起任务负责原来的博客审阅,保留问题、会话和操作编号;维护任务负责检查日志、修代码和验证安装。实际页面的结构证据由发起任务提供,维护任务用它制作离线测试。修复后仍由发起任务继续原操作。这样可以同时推进调查,又避免两边各自提交一次或抢着读取同一个结果。
flowchart TD
R["Saved failure record"] --> D["Delivery evidence"]
D --> S["Scoped page observations"]
S --> F["Offline reproduction"]
F --> P["Shared adapter repair"]
P --> V["Runtime and workflow verification"]
图 3:本轮排障的工作顺序。先确认已经发生了什么,再用有限的页面观察重现故障;代码测试通过后,还要区分运行版本验证与原任务验收。
一个具体例子:输入框明明有字,为什么程序说不对
修好输入框定位后,下一次运行报告“输入内容不是完整的原问题”。如果只看旧日志里的字符数 0,很容易判断输入失败。但那条日志记录的是输入前的状态,不能用来解释输入后的问题。检查一个字段时,要同时知道它在哪个阶段采集。
页面观察随后证明,文字已经在富文本编辑器里。程序却优先读取了它的 value 属性:这个属性在普通文本框里表示内容,在本次观察到的可编辑 DIV 上却是空字符串。这排除了“没有输入文字”这个解释,问题转向了“用什么方式读文字”。
直接换成可见文本也没有解决:原问题规范化后是 730 个字符,按页面布局读取的 innerText 是 733,直接拼接文本节点的 textContent 是 724。这三个长度只能说明表示方式不同,不能证明内容相同。继续比较完整字符串,才发现前者多出的内容恰好是三个 URL 前的空格;后者则丢掉了段落间隔。页面中的段落元素和行内 URL 包装解释了差异。
因此修复放在把页面结构还原为文字这一层:保留段落和换行,连续拼接行内文字,然后仍与完整原问题比较。程序没有把“差三个字符”当成可以接受,也没有删除所有空格来制造相等。这个例子最值得学的是:当两个值不一致,先查它们是否表达同一层的东西,再决定比较规则是否错误。AO 保留了具体修复与测试。
文字已经留在编辑器里,也不能直接重新输入。保存的记录必须证明这次操作从未获准发送,当前标签页和全文又确实属于原操作,程序才能保留这份草稿继续。否则,所谓“重试”可能覆盖用户另写的内容。修复读取方式与允许继续发送,是两个需要分别论证的决定。
为什么每修一步,又出现下一步的问题
前面的检查会挡住后面的执行。输入框没找到,就观察不到输入比较;输入比较没通过,就到不了 Send;Send 没识别,就不会经历新会话地址和消息结构。几个不同的旧假设藏在同一条路径上,逐层修复后才逐层暴露。
但这不能只怪网页变了。我们早期的测试夹具——供测试使用的简化页面——保留了新输入框,却继续使用旧发送按钮和旧消息节点。测试确实执行了流程,但执行的是我们拼出来的页面组合。它没有验证实际观察到的那一套界面。
后来的修复把输入框定位、文字还原、消息身份识别等规则收进共享函数,让准备、发送确认和读取使用相同规则;测试也同时保留新输入框、新 Send、用户消息、助手回复及其 Copy 控件。共享函数减少同一个修复漏改另一处的机会,完整夹具则检查这些函数连在一起是否成立。它们解决的是两个不同的遗漏。
对我来说,这轮最应改进的工作方式是:第一次发现页面结构不兼容时,就把相邻阶段的结构一起核对,而不是用下一次真实请求来发现下一处选择器失效。 已经出现的消息可以只读检查;只有发送后才会出现的部分,要明确列为尚未验证,不能拿旧页面结构填补后就宣布覆盖完成。
哪些看起来像修复,其实证据还不够
有一段时间,页面的结构计数几分钟不变,Stop 按钮仍在。我考虑过后台渲染停滞,但页面后来在没有做前台激活的情况下自行完成。历史上出现过后台渲染问题,不等于这次也是同一个原因。把“值得检查的解释”直接写成“已经查明的原因”,会引入不必要的唤醒、刷新或超时补丁。
另一个容易误判的信号是 Copy。新版页面中,用户消息有 Copy message,回复的表格有 Copy table,整篇回复另有 Copy。甚至用户与助手还共享一个外层容器。看到 Copy 或外层消息编号,都不足以证明“这篇回答属于我的问题,而且已经完成”。程序必须分别识别原用户消息、助手正文及其完成控件,再结合生成状态判断。AR 的测试专门放进这些容易混淆的相邻控件。
最后,扩展握手只证明当前加载了某个版本。它既没有上传 PDF,也没有提交问题,更没有验证回答内容。离线测试、版本握手、恢复读取、正常新请求,是四种不同证据,不能互相代替。把它们都叫“测试通过”,读者和后续维护者就无法知道还缺什么。
调试过程本身也会制造故障
这次我为了保留本地博客预览,把构建产物放进 tmp。首次检查发生在产物生成之前,因此通过了;后续另一个任务发布时,类型检查把这些生成的 JavaScript 也纳入了源码范围,最后耗尽内存。tmp 被 Git 忽略,并不能约束类型检查工具。这是我安排预览目录时遗漏了配置边界,和 ChatGPT 页面无关。
修复保留严格检查,只排除生成目录,并在产物仍存在时重新运行检查。增加内存上限可能让错误的工作多跑一会儿,却没有解释为什么要检查打包产物。这个问题现在补在 AS,也写进现有预览 skill,要求后续任务检查继承后的有效配置。
同时,本地更新不能混进其他任务获准的公开发布。我们在两次发布窗口中保存两篇文章的本地内容,临时提供旧的公开构建输入,发布后核对并恢复。这种协调完成了当次任务,但步骤仍依赖人工交接,适合列入后续流程改进,不能说已经有了自动隔离发布的机制。
目前哪些结论站得住
| 检查对象 | 已有证据 | 还不能据此声称什么 |
|---|---|---|
| 新版页面适配 | 完整离线页面组合通过,实际扩展版本已观察到 | 所有未来布局都兼容 |
| 原失败请求的答案 | 一次绑定原消息的恢复读取,10,907 字符、746 行 | 原失败发送记录已经变成成功 |
| 后续新文本请求 | 同一会话内发送、确认、读取正常完成,4,590 字符、213 行,无恢复读取 | 新版 PDF 上传也重新验收过 |
| 博客审阅内容 | 发起任务核对了实际反馈涉及的文章结构 | 固定部署不可访问时,仍完整审阅了该快照的所有文章 |
| 本地预览构建 | 产物存在时检查通过,生成 HTML 与服务内容一致 | 已完成所有屏幕尺寸的视觉验收 |
这些记录说明本轮文本工作流恢复了,但不足以算出整体成功率。历史上还有一例缺少完整现场证据的地址异常没有查明根因,见 Q。保留“未知”很有价值:后续若再发生,可以收集缺失的证据;若现在把它随手归到某个已修 bug 下,反而会失去这个提醒。
10. 接下来怎样减少反复
以下是根据本轮故障提出的工程计划,不是已经完成的功能,也没有因此启动新的线上测试或后台任务。顺序先补证据和测试,再决定是否需要更大的代码调整。
先把真实页面组合变成可重复的验收材料
现有新消息夹具已经补齐了本轮主要结构。下一步应给保存的简化页面附上观察日期和覆盖阶段,明确哪些元素来自同一版界面,哪些只是测试数据。每次页面适配修改,都运行完整的“准备 → 确认 → 读取”路径,并保留最接近合法情况的反例:同名但不属于当前表单的按钮、相同文字但不同消息身份的历史问题、表格自己的 Copy。
验收标准不是多出多少测试,而是旧实现能在相关夹具上失败、修复后同一夹具通过,相邻错误情况仍被拒绝。已有的完整连接测试要继续使用真实的参数序列化和注入函数,避免把正在排查的边界替换成总能返回正确答案的模拟函数。
再减少只能靠下一次调用发现的问题
已有只读诊断主要覆盖特定的、可证明未发送的失败。计划评估是否需要把阶段、当前目标、消息身份和完成控件的有限诊断统一起来,让错误结果直接回答“哪个条件没满足”。诊断仍应与发送分开,不通过再发一次来探测页面。
验收时要看拒绝是否可解释:记录来自输入前还是输入后、实际观察到了几个候选、ID 是否唯一、当前路径是临时还是正式、读取的是哪一个回合。未知状态应明确返回未知。诊断不需要保存整篇私有问题或回复来证明这些结构事实,也不能为了方便而获得任意页面操作能力。
补足生命周期测试,再做有限的真实验收
一张干净页面上的成功,只覆盖工作流的一小部分。下一轮测试矩阵应优先补以下状态转换:
| 场景 | 想验证的风险 | 通过条件 |
|---|---|---|
| 已发送旧 PDF,再准备不同 PDF | 旧文件选择残留被误认成新草稿 | 当前附件正确识别,真实草稿和未完成上传仍受保护 |
| 有历史消息,再发送新问题 | 重复文本或共享容器造成错误归属 | 只接受原操作对应的新消息与回复 |
| 后台、最小化与持续生成 | 把慢生成误判为渲染故障 | 记录可解释的状态变化,清理临时资源,不额外发送 |
| 扩展等待中重启、确认回调丢失 | 状态丢失导致重复操作 | 保留发送证据,恢复不能重新获得同一发送权限 |
| 用户修改草稿或继续发言 | 自动化覆盖或误读用户的新内容 | 保留用户内容,拒绝不再满足身份约束的操作 |
这些场景有些已有局部回归,计划要补的是它们与最新页面适配连在一起的覆盖情况。离线测试先行;需要真实网页、账户或文件的验收,再沿用明确获准的操作范围。最新文本请求已经通过,PDF 路径仍应单独记录结果。没有运行的组合保持“未验证”,不沿用旧版本的通过状态。
用任务是否完成来衡量改进
当前 reference 包含代码缺陷、合理拒绝、测试问题和未知事故,条目数不等于故障数。一个问题经历几次可证明未发送的准备失败、一次发送和一次恢复读取,也不是几次独立业务失败。
计划利用现有操作编号和失败父子记录,按一个逻辑请求汇总:首次是否完成、是否通过只读恢复完成、是否因真实草稿或权限条件停止、是否缺少足够证据。测试请求与日常使用请求分开。这样才能判断维护是否减少了用户等待和人工接手,而不是只看到绿色测试数量上涨。目前没有整理出这样的生产统计,因此本文不提供成功率。
发布流程也应采用同样的思路:在构建开始前确定本次获准发布的内容,把本地预览输出留在检查范围之外,并验证构建后的工作区仍能检查通过。现有备份和交接解决了这两次发布;若并行编辑继续频繁发生,再把这套检查固化成一个小的、可回退的构建步骤,避免重复依赖口头提醒。
这轮工作没有改变最初的目标:一次获准的问题,取回与它对应的完整回答。真正的进展是让每一步的依据更明确,并用恢复读取和正常新请求分别验证了结果。接下来应优先减少“测试通过,但真实组合没覆盖”的缺口,让失败更早、更准确地暴露,而不是承诺网页以后永远不变。
Comments