Skip to content

fix: stop the chat composer rewriting typed characters (issue #1399) - #1429

Merged
sakibsadmanshajib merged 2 commits into
mainfrom
fix/1399-composer-literal-input
Aug 29, 2026
Merged

sakibsadmanshajib merged 2 commits into
mainfrom
fix/1399-composer-literal-input

Conversation

@sakibsadmanshajib

@sakibsadmanshajib sakibsadmanshajib commented Aug 29, 2026 •

Copy link
Copy Markdown
Owner

Fixes #1399.

What was wrong

The chat composer rewrote characters as they were typed, and the rewritten string is what reached the model. It is not a display bug: the composer DOM, the outbound POST /api/chat/completions body and the rendered transcript all agree with each other, and all three differ from what was typed.

So a user who types git push --force asks the model about git push —force, and nothing on screen says the text was changed.

Reproduced as a controlled pair, because a QA pass could not reproduce it

An independent QA pass the same day typed straight quotes and a double hyphen into the composer on the deployed box, captured the outbound body, and found it byte identical. That is a real measurement and it contradicted the issue, so this was settled by experiment rather than by argument.

Both arms run the same pinned backend image the deploy uses, ghcr.io/open-webui/open-webui:v0.10.2@sha256:9fcea9c6..., with a locally built frontend bind mounted over /app/build, which is exactly the arrangement deploy/docker/Dockerfile.open-webui produces. Same image digest, same container environment, same script, same payload, same headless Chromium, same one-keystroke-at-a-time input (page.keyboard.type; pasting does not fire input rules and proves nothing). The only difference is which frontend build is mounted.

Payload: PROBE1399 it's a "test" -- ok a != b f() -> int

before, origin/main frontend

typed             PROBE1399 it's a "test" -- ok a != b f() -> int
1 composer DOM    PROBE1399 itU+2019s a U+201CtestU+201D U+2014 ok a U+2260 b f() U+2192 int
2 outbound body   PROBE1399 itU+2019s a U+201CtestU+201D U+2014 ok a U+2260 b f() U+2192 int

after, this branch

typed             PROBE1399 it's a "test" -- ok a != b f() -> int
1 composer DOM    PROBE1399 it's a "test" -- ok a != b f() -> int
2 outbound body   PROBE1399 it's a "test" -- ok a != b f() -> int

Note != becoming U+2260 and -> becoming U+2192 in the before arm. Neither is in the issue, and neither would have been fixed by the configure() call the issue suggested.

This does not explain what the QA pass saw, and it does not try to. The likely explanation is that richText={$settings?.richTextInput ?? true} is a per-user setting and the extension is registered only inside the richText branch, so with rich text off there are no input rules to fire at all; an input method that does not dispatch key events would do it too. Both are consistent with every observation on record and neither changes the fix. Full detail in docs/proof/composer-literal-input-2026-08-29/capture.md.

Root cause, and which layer holds it

vendor/open-webui/src/lib/components/common/RichTextInput.svelte imported Typography from @tiptap/extension-typography and registered it bare, with no configure(), inside the richText ? [...] : [] branch.

Those rules are ProseMirror input rules. They rewrite the text buffer at the moment a character is typed, not its presentation, which is why the mutation survives serialization and leaves the browser.

This is the frontend layer, and it is the layer that is actually built. Per .wolf/decisions.md D-044, deploy/docker/Dockerfile.open-webui builds only the frontend from vendor/open-webui and replaces /app/build, so a change here is live. The other two layers were checked and ruled out rather than assumed:

  • deploy/docker/owui-patches/ rewrites the pinned upstream backend image. Nothing in that directory references RichTextInput, Typography or the editor; the only composer matches are backend Python for credits and RAG config. It would also be the wrong tool, since the mangling happens before the request is sent.
  • deploy/docker/Caddyfile.owui is a reverse proxy and never sees the character stream before send.

No exact-literal bundle patch matches text this change moves, so nothing under owui-patches/ breaks.

Why remove the extension rather than configure the rules off

The issue proposed Typography.configure({...}) with six rules disabled. Read against @tiptap/extension-typography@3.20.2, the version the lockfile pins, that is incomplete: the extension registers twenty two input rules, and the six named leave sixteen live, including these.

Rule Typed Becomes Breaks
rightArrow -> → Go, Rust, C, PHP
notEqual != ≠ nearly every language
laquo / raquo << >> « » bit shift, C++ streams, shell redirect
leftArrow <- ← R, Haskell
multiplication 2 * 3 2×3 arithmetic, globs
superscriptTwo ^2 ² regex anchors, XOR
plusMinus +/- ± tolerances
copyright and friends (c) (r) (tm) © ® ™ short parenthesised identifiers
oneHalf and friends 1/2 1/4 ½ ¼ paths, fractions

What a user loses. Everything the extension does, which is automatic typographic prettifying while typing prose: curly quotes, a real em dash from --, a single ellipsis glyph, ©, ®, ™, ±, ≠, arrows and vulgar fractions. Nothing else; the extension defines only name, addOptions and addInputRules, so there is no command, mark, node or shortcut to lose with it.

That is a real cost and it is worth naming. The judgement is that on a coding assistant, where people type shell flags, comparisons and string literals into the composer constantly, silent prettifying is a liability rather than a feature, and a user who wants an em dash can still type one, which is covered by a test. Anyone who disagrees can reinstate it with configure() and the guard will tell them exactly which rules they turned back on.

Scope

RichTextInput is shared by the chat composer, the channel composer, InputModal and NoteEditor, and all of them take the richText default of true. So the Notes editor also loses smart typography, not just chat. That is deliberate rather than incidental: silently rewriting literal input is the defect class, not a chat-only symptom, and fixing it once where all four callers route through is a smaller diff than four guards. Calling it out because it is a real, if minor, behaviour change on a surface the issue does not mention.

Code inside a formed fence was always exempt, because input rules do not run in a code block. That is unchanged, this change only removes rules, and there is now a test that types into a real code_block node with the full rule set active to pin it.

One thing this deliberately does not do: the repository bans dash punctuation in prose that agents write. That rule constrains generated prose. It is not implemented here as a filter over user input, because the whole point of the issue is that the user's literal characters survive, an em dash included, when the user is the one who typed it. There is a test for exactly that.

Test

vendor/open-webui/src/lib/hive/composer-literal-input.test.ts, 5 tests.

The repo has no DOM harness for the chat frontend, so rather than assert on the DOM (which would be the wrong assertion anyway, since the issue is about what is sent) the test types a corpus through a real ProseMirror input-rule pipeline and asserts the string the editor holds afterwards, which is the string the send path serializes. ProseMirror's model and state are pure JavaScript; only the view needs a DOM. The matching and range arithmetic mirror run in @tiptap/core@3.20.2, and the replacement is the library's own textInputRule handler rather than a reimplementation.

What keeps it honest:

  • It pins the harness against the corruption measured in the before arm above, so a harness that stopped reproducing the defect fails rather than reporting a false pass.
  • Its rule set is keyed on the module each extension is imported from, not the identifier it is bound to. Verified by mutation, not asserted: re-adding the extension as import SmartText from '@tiptap/extension-typography' makes the typing case fail with expected 'git push —force' to be 'git push --force'. An earlier revision keyed on the literal name Typography and was demonstrably blind to that rename.
  • The module check is an allowlist of reviewed modules, not a denylist naming one, so a newly added rewriting extension fails until someone looks at it.

Where it runs, verified rather than claimed. The first revision of this pull request asserted it ran in CI and that was false: scripts/test-owui-hive-frontend.sh copies src/lib/hive into a scratch tree and installed only vitest, coverage, svelte and the svelte vite plugin, so no @tiptap package could resolve and the required Repo policy lints (tenant + audit) check went red. This was the first test in that directory to need a third party runtime dependency. Fixed by pinning the three @tiptap packages into that install from vendor/open-webui/package-lock.json, the same mechanism svelte already uses, and by copying RichTextInput.svelte into the scratch tree since the guard reads it. Proven by running that lane's own script locally:

$ sh scripts/test-owui-hive-frontend.sh
 ✓ lib/hive/composer-literal-input.test.ts (5 tests) 260ms
 Test Files  19 passed (19)
      Tests  251 passed (251)
EXIT=0

It also runs at image build time via RUN npm run test:frontend -- --run in deploy/docker/Dockerfile.open-webui, where the full suite is 251 passed and npm run build succeeds.

No new application dependency. vendor/open-webui/package.json and package-lock.json are unchanged.

Buglog entry

{"id":"1399-composer-smart-typography","date":"2026-08-29","title":"Chat composer rewrote typed characters and the rewritten text reached the model","error_message":"Typing `git push --force` into the chat composer sent `git push —force`; straight quotes became curly, `--` an em dash, `...` an ellipsis glyph, and `->`, `!=`, `<<`, `>>`, `2 * 3`, `^2` were also rewritten","root_cause":"vendor/open-webui/src/lib/components/common/RichTextInput.svelte registered @tiptap/extension-typography bare in the richText extension array. Its twenty-two entries are ProseMirror input rules, which rewrite the text buffer as a character is typed rather than its presentation, so the mutated string is what the editor serializes and what leaves the browser. Code inside a formed code block was exempt because input rules do not run there, which made the defect look narrower than it was and sent an earlier hunt to the marked renderer and to CSS ligatures, neither of which is the layer. A same-day QA pass could not reproduce it on the deployed box, most likely because richText is a per-user setting and the extension is registered only inside that branch.","fix":"Removed the import and the registration. The extension contributes nothing but input rules, so dropping it removes all twenty-two; the suggested configure() with six rules disabled would have left sixteen live, including -> and !=. Guarded by vendor/open-webui/src/lib/hive/composer-literal-input.test.ts, which types a corpus through a real ProseMirror input-rule pipeline with no DOM, asserts the serialized string, pins the harness against the measured corruption so it can go red, and keys on the imported module rather than the identifier so an aliased re-add is caught. Proven with a before/after wire capture on one pinned backend image differing only in the mounted frontend build.","tags":["chat","open-webui","composer","tiptap","prosemirror","frontend","issue-1399"]}

The composer rewrote characters as they were typed, and the rewritten
string is what reached the model. Measured live: the composer DOM, the
outbound POST /api/chat/completions body and the rendered transcript all
agreed with each other and all three differed from what was typed, so a
user asking about `git push --force` got an answer about `git push
—force` and nothing said the text had been changed.

The cause is @tiptap/extension-typography, registered bare in the
composer's rich text extension list. Its rules are ProseMirror input
rules, which rewrite the text buffer rather than its presentation, so the
mutation survives into the request body.

The issue proposed disabling six of those rules. Read against the package
source that is incomplete: the extension registers twenty-one, and the
six named leave `->`, `!=`, `<<`, `>>`, `+/-`, `2 * 3` and `^2` still
rewriting. The extension contributes nothing but input rules, so dropping
it removes every one of them and costs the composer nothing.

Code inside a formed fence was always exempt, because input rules do not
run in a code block. Code typed as prose was not, and on a coding surface
that is the common case.

The guard types a corpus through a real ProseMirror input-rule pipeline,
with no DOM, and asserts the string the editor holds afterwards, which is
the string the send path serializes. It first pins the harness against
the corruption measured on the deployed box, so a harness that stopped
reproducing the defect fails rather than reporting a false pass. Its rule
set is derived from RichTextInput.svelte itself, so re-adding the
extension turns the byte identity case red through simulated typing.
@coderabbitai

coderabbitai Bot commented Aug 29, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 35 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fa5ac559-181e-45d5-9903-ba95dd7821bd

📥 Commits

Reviewing files that changed from the base of the PR and between 908e48a and 7b5fa31.

📒 Files selected for processing (4)
  • docs/proof/composer-literal-input-2026-08-29/capture.md
  • scripts/test-owui-hive-frontend.sh
  • vendor/open-webui/src/lib/components/common/RichTextInput.svelte
  • vendor/open-webui/src/lib/hive/composer-literal-input.test.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sakibsadmanshajib

Copy link
Copy Markdown
Owner Author

Review stream 1 of 3: CodeRabbit CLI

coderabbit review --base main --agent, version 0.7.5, ran and completed. Status review_completed, 1 finding, both changed files reviewed.

Finding, severity critical: vendor/open-webui/src/lib/hive/composer-literal-input.test.ts line 219, reported as "duplicated trailing content after the final assertion, including the extra describe closure and repeated Typography extension test, so the file has only one valid closing block and parses correctly".

Rebuttal, not accepted. The duplication does not exist. Checked against the file rather than against the description:

describe count:                  1
it( count:                       4
"registers no Typography..." :   1
last line:                       });
file length:                     228 lines

The file has one describe block, four it cases, one Typography assertion, and one closing brace pair. The claim that the file does not currently parse is also falsified directly: Vitest parses and runs it, reporting Tests 4 passed (4), and it is one of the 19 files in the green 250 passed full frontend suite. A file that failed to parse would be a collection error, not a pass.

Reading this as a likely artifact of the reviewer seeing an overlapping diff hunk rather than the resulting file. No change made.

No other findings from this stream.

Comment thread vendor/open-webui/src/lib/hive/composer-literal-input.test.ts
Comment thread vendor/open-webui/src/lib/hive/composer-literal-input.test.ts
Comment thread vendor/open-webui/src/lib/hive/composer-literal-input.test.ts Outdated
Comment thread vendor/open-webui/src/lib/hive/composer-literal-input.test.ts Outdated
Comment thread vendor/open-webui/src/lib/hive/composer-literal-input.test.ts Outdated
Comment thread vendor/open-webui/src/lib/hive/composer-literal-input.test.ts
Comment thread vendor/open-webui/src/lib/components/common/RichTextInput.svelte Outdated
@sakibsadmanshajib

Copy link
Copy Markdown
Owner Author

Review stream 2, TypeScript and JavaScript domain review. Verdict: BLOCK, on one critical and two high findings, all posted inline.

Merge readiness first, since it changes what to do next. The PR is a draft and mergeable_state is blocked. The required check Repo policy lints (tenant + audit) is failing on fcb3126, and it is failing because of this PR, on the test this PR adds. Nine other required checks were still pending at review time. So this is not a review that should wait for CI: CI has already returned the most important finding.

Inline findings, in order of weight:

  1. CRITICAL, test file, line 5. The guard never executes in CI. make test-owui-frontend reports composer-literal-input.test.ts (0 test) and Failed to load url @tiptap/pm/model. The scratch harness in scripts/test-owui-hive-frontend.sh installs only vitest, coverage-v8, svelte and the svelte vite plugin, so no @tiptap package can resolve there. Fixable by pinning the three @tiptap packages from the lockfile into that install, the same way svelte already is.
  2. HIGH, test file, line 97. composerRules() is bypassable. Measured, not guessed: an aliased default import with double quotes re-registers the extension and all four tests still pass. With single quotes only the line 217 source assertion catches it, never the corpus case. The corpus case is a source string match wearing a simulation costume.
  3. HIGH, test file, line 151. as never on the handler argument, which the repo standard bans and which is broader than any. Nothing in CI typechecks vendor/open-webui at all, so this cast has no backstop.
  4. LOW x3 plus a count correction on the Svelte comment. Details inline.

What I verified and found sound, so it is not re-litigated later:

  • The fix itself is correct and complete. I read @tiptap/extension-typography@3.20.2 (the locked version): the extension defines only name, addOptions and addInputRules, so removing the registration removes every rewrite and nothing else. configure() with six rules disabled would indeed have left sixteen live.
  • The four corruption expectations are exactly right. I ran the test file locally against the locked @tiptap versions in a mirror of the scratch tree: 4 passed. The harness reproduces it is a "test" -- ok becoming curly quotes plus an em dash, and git push --force becoming git push —force.
  • typeInto mirrors run in @tiptap/core faithfully on the parts that matter: textBefore construction via the library own getTextContentFromNodes, the range.from = from - (match[0].length - char.length) arithmetic, the handler === null || !tr.steps.length bail, and first-rule-wins ordering. The one divergence is an extra guard, covered in the LOW comment.
  • Nothing else in the file or its consumers references Typography, and nothing in deploy/docker/owui-patches/ or owui-static/ matches it, so no bundle rewrite breaks.

One scope note for the record, not a defect. RichTextInput has four consumers, and NoteEditor.svelte is one of them. It passes no richText prop at all, so it takes the export let richText = true default and loses smart typography too. On the chat composer that is the whole point of the fix. On a notes surface, em dashes and curly quotes were arguably a feature. The PR body owns this scope choice, and I agree with it (silently rewriting literal input is the defect class), but a notes user will see prose stop prettifying, and that should be a recorded decision rather than a surprise.

Addresses review on #1429.

The guard never ran. `scripts/test-owui-hive-frontend.sh` copies `src/lib/hive`
into a scratch tree and installed only vitest, coverage, svelte and the svelte
vite plugin, so no `@tiptap` package could resolve there and the file failed to
collect, taking the required `Repo policy lints (tenant + audit)` check red.
This was the first test in that directory to need a third party runtime
dependency. The three `@tiptap` packages are now pinned into that install from
`vendor/open-webui/package-lock.json`, the same way svelte already is, so the
lane runs the versions the image build resolves. `RichTextInput.svelte` also
travels into the scratch tree now, since the guard reads it.

The guard also claimed detection power it did not have. Keying on the literal
identifier `Typography` meant an aliased import re-corrupted the composer and
still passed. It now maps every imported identifier to the module it came from
and keys on the module, so a rename is caught, and the check is an allowlist of
reviewed modules rather than a denylist naming one, so a newly added rewriting
extension fails until someone looks at it. Verified by mutation rather than
asserted: with the extension re-added as `SmartText`, the typing case fails
with `expected 'git push —force' to be 'git push --force'`.

Also from review: the rule count is twenty two, not twenty one; the
`matchedDocLength` re-validation is not in `@tiptap/core`'s `run` and its only
failure direction was a false pass, so it is gone; the extension array slice
anchored on the first of three `...(richText` occurrences and landed inside
`StarterKit.configure`, so it now anchors on the array branch and asserts there
is exactly one; the code block exemption was unreachable in the harness schema
and is now a real `code_block` node with a test that types into it; and the
`as never` cast is replaced by a narrowed call whose type names the three
fields `textInputRule` actually reads.

Adds the wire-level capture. The defect and the fix were run as a controlled
pair on the same pinned backend image with the same script and payload,
differing only in which frontend build was mounted. Before, the composer DOM
and the outbound request body both carry U+2019, U+201C, U+201D, U+2014, U+2260
and U+2192 where straight quotes, a double hyphen, `!=` and `->` were typed.
After, all three readings are byte identical. That also answers the same-day QA
pass that could not reproduce the defect on the deployed box.
@sakibsadmanshajib
sakibsadmanshajib marked this pull request as ready for review August 29, 2026 17:19
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@sakibsadmanshajib

Copy link
Copy Markdown
Owner Author

Visual proof

Issue #1399, controlled pair on one pinned backend image, differing only in the mounted frontend build. Payload typed one keystroke at a time: PROBE1399 it's a "test" -- ok a != b f() -> int. Frame 1, origin/main frontend: the composer holds curly quotes, an em dash, a not-equal sign and a right arrow, and the intercepted POST /api/chat/completions body carries the same corruption. Frame 2, this branch: straight quotes, literal double hyphen, != and -> all survive, and the request body is byte identical to what was typed. Wire readings in docs/proof/composer-literal-input-2026-08-29/capture.md.

pr1429-20260829172539-14834-i1399-01-before-origin-main.png

pr1429-20260829172543-31357-i1399-02-after-this-branch.png

@sakibsadmanshajib

Copy link
Copy Markdown
Owner Author

Review streams, consolidated

Three streams ran. None was skipped.

Stream 1, CodeRabbit CLI 0.7.5. Ran and completed, 1 finding, rebutted as a false positive with counter-evidence in a comment above. No change made.

Stream 2, TypeScript domain reviewer. Seven inline findings, one critical, two high, four low. All seven addressed and resolved: six fixed, and one (as never) fixed in substance with the "build a structurally valid six field object" half rebutted, because the remaining three fields are the tiptap CommandManager surface and cannot be constructed without a live Editor. This was the stream that earned its keep. It caught that the guard did not run in CI at all, and it caught by measurement rather than by reading that an aliased re-import defeated the guard entirely. Both were true, and the pull request body had asserted the opposite of the first.

Stream 3, plain adversarial pass over my own diff. One finding, self-caught before stream 2 posted and already fixed in the same push: the first revision used as never on the rule.handler call, which this repository bans. Everything else I checked held up, including the range arithmetic against @tiptap/core's own run, and the four consumers of RichTextInput (chat/MessageInput, channel/MessageInput, InputModal, NoteEditor) for anything else that read the removed symbol. Nothing did.

Domain specialists not added. This diff touches no auth, money or input-parsing path in the security sense, so the mandatory security and database review did not trigger and /codex:adversarial-review was not added. Saying so explicitly rather than leaving the absence to be read as an oversight.

Verification summary

What Result
Repo policy lints (tenant + audit) lane, run locally by its own script Test Files 19 passed (19), Tests 251 passed (251), EXIT=0
Full chat frontend suite in the image build lane 251 passed
npm run build for the chat frontend succeeds, build/index.html present
Guard mutation test, plain re-add 2 of 5 fail, caught
Guard mutation test, aliased re-add as SmartText 2 of 5 fail, caught, including the typing case
npm run lint:proof-tokens ok, 223 files scanned
Wire capture, before and after corruption present then absent, byte identical to typed input after

@sakibsadmanshajib
sakibsadmanshajib merged commit 229ce42 into main Aug 29, 2026
29 checks passed
@sakibsadmanshajib
sakibsadmanshajib deleted the fix/1399-composer-literal-input branch August 29, 2026 17:29
sakibsadmanshajib added a commit that referenced this pull request Aug 29, 2026
## Summary

This is the batched buglog follow-up for the pull requests merged to
`main` on 2026-08-29. Its diff is `.wolf/buglog.jsonl` and nothing else.

Per `.claude/rules/openwolf.md`, every fixed bug, error, failed test or
failed build must be logged, but the line may never be appended on a fix
branch. `merge=union` in `.gitattributes` resolves concurrent appends
locally and is ignored by GitHub's server side merge, so two branches
that both appended land in hard conflict there. An unmergeable pull
request gets no `refs/pull/N/merge`, no `pull_request` run and therefore
zero checks, and the required status gate then blocks the merge for a
reason the page never states (issue #873). Each fix accordingly carried
its entry in its own pull request body, and this pull request copies
them onto `main` in one batch, which the protocol explicitly prefers
over one pull request per entry.

## Scope examined

Fifty nine pull requests merged to `main` on 2026-08-29. Forty eight of
them carried at least one entry, for eighty two entries in total. Thirty
two of those were already on `main` and are skipped, leaving fifty
appended here from thirty four pull requests.

The largest block of skips comes from #1342, the equivalent batch for
the 2026-08-28 merges, which merged earlier the same day and already
landed thirty six entries covering #1257, #1268, #1276, #1277, #1287,
#1292, #1293, #1294, #1296, #1301, #1303, #1305, #1313, #1335 and #1337.

## What landed

Fifty entries appended, one JSON object per line, append only. The 232
pre-existing lines are byte identical to `origin/main` (verified by
hashing the first 232 lines of the result against the base file). Every
line in the resulting file parses as JSON and carries `error_message`,
`root_cause`, `fix` and `tags`.

| Source | Entries |
|---|---|
| #1083 | 2 |
| #1277 | 1 |
| #1278 | 1 |
| #1298 | 1 |
| #1334 | 1 |
| #1336 | 3 |
| #1343 | 1 |
| #1346 | 1 |
| #1351 | 1 |
| #1365 | 2 |
| #1368 | 1 |
| #1369 | 1 |
| #1371 | 3 |
| #1375 | 3 |
| #1376 | 1 |
| #1378 | 1 |
| #1379 | 2 |
| #1388 | 5 |
| #1389 | 3 |
| #1390 | 2 |
| #1393 | 1 |
| #1394 | 1 |
| #1410 | 1 |
| #1417 | 1 |
| #1421 | 1 |
| #1423 | 1 |
| #1424 | 1 |
| #1426 | 1 |
| #1429 | 1 |
| #1431 | 1 |
| #1433 | 1 |
| #1434 | 1 |
| #1436 | 1 |
| #1439 | 1 |

Entries are copied verbatim from their source pull request bodies.
Nothing was rewritten, no field was invented, and no field was added. No
JSON needed repair: all eighty two extracted entries parsed on the first
attempt and all four required fields were present on every one.

## Merged pull requests that carried no entry

Eleven of the fifty nine. Recorded here because the gap is itself the
useful signal.

| Pull request | Title | Assessment |
|---|---|---|
| #1013 | chore(deps): bump the go-minor-patch group across 1 directory
with 4 updates | Dependabot bump, no defect fixed, no entry expected |
| #1015 | chore(deps): bump the go-minor-patch group across 1 directory
with 6 updates | Dependabot bump, no entry expected |
| #1016 | chore(deps): bump golang from 1.26-alpine to 1.27-alpine in
/deploy/docker | Dependabot bump, no entry expected |
| #1218 | chore(deps): bump postcss from 8.5.19 to 8.5.26 in
/apps/desktop | Dependabot bump, no entry expected |
| #1219 | chore(deps): bump golang.org/x/crypto from 0.41.0 to 0.52.0 in
/apps/control-plane | Dependabot bump, no entry expected |
| #1342 | chore: batch buglog entries for the 2026-08-28 merges | The
previous batch pull request itself, correctly carries no entry of its
own |
| #1364 | chore: remove four dead skills and record the patterns that
cost time | Protocol gap. The body records patterns that cost time,
which is the shape of a buglog entry, but none was written as one |
| #1383 | test: retire stale expected-failure markers, restore the ones
that are true (#1381, #1382, #1324) | Protocol gap. Stale `it.fails`
markers reading as red is a real defect that was fixed here and should
have carried an entry |
| #1384 | docs: correct D-047, hive-auto reverted to variable pricing
(D-059) | Decision ledger correction, arguably a documentation defect,
no entry written |
| #1387 | chore(deps): bump next from 15.5.23 to 16.3.3 in
/apps/agent-console | Dependabot bump, no entry expected |
| #1398 | docs: rescue the 2026-08-25 parity captures and add the
2026-08-29 QA matrix evidence | Documentation and evidence rescue, no
entry written |

Six of the eleven are Dependabot bumps and one is the previous batch, so
the genuine protocol gaps are #1364, #1383, #1384 and #1398. Of those,
#1383 is the one worth a follow-up: it fixed a real defect class (a
stale expected-failure marker reads as a red "Expect test to fail" and
gets dismissed as pre-existing) and left no record.

## Entries skipped as already present

Thirty two. Thirty of them matched an entry already on `main` on
`error_message`, `id` or `fix`. Two more from #1278 are semantic
duplicates that an exact match would have missed, and were skipped after
reading the landed entries they duplicate:

- #1278's `streaming content_block_start omits text field` entry is
covered by the consolidated
`bug-2026-08-28-anthropic-sdk-wire-conformance` entry landed from #1296,
whose root cause names the same `omitempty` on
`StreamContentBlock.Text`.
- #1278's `GET /v1/models leaked an upstream provider name` entry is
covered by `BUG-1284`, landed from #1300, which names the same
`public.model_aliases.summary` publication path.

#1278's third entry, on `top_k` forwarding producing a 400, is not
covered anywhere on `main` and is appended here. #1342 recorded #1278 as
fully "merged into #1296", which was accurate for two of its three
entries.

## Note on entry quality

One appended entry is thin: #1277's parity re-score record carries
`error_message` of `n/a` and a root cause of "console had no
privacy/data-policy surface at all". It is a parity gap record rather
than a defect record. It is included exactly as written rather than
embellished, per the protocol's preference for the author's own words.

## Test plan

- [x] Branch cut fresh from `origin/main`, diff is `.wolf/buglog.jsonl`
and nothing else
- [x] First 232 lines byte identical to the base file (md5 match)
- [x] All 282 resulting lines parse as JSON and carry `error_message`,
`root_cause`, `fix` and `tags`
- [x] No `.wolf/` telemetry (`anatomy.md`, `memory.md`,
`token-ledger.json`, `hooks/_session.json`, `buglog.json`) in the commit
- [ ] The six required checks report green via the inert path allowlist
in `.github/workflows/ci.yml`

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant