| Document | draft-fossil-sync-protocol-00 |
| Section | 5.2 — Card Syntax (file, cfile and uvfile handler failures); 10.1 — Error Card |
| Type | Technical (compatibility note — the reference server URL-encodes one family of error texts; the draft's encoding is kept) |
| Status | Proposed — operator decision pending |
| Reported | 2026-10-11 |
| Submitter | Dan Mestas |
| Found via | go-libfossil's conformance suite against Fossil 2.28; confirmed by reading Fossil's source and by sending it a malformed card |
1. Summary
§5.2 says a validation failure in the file, cfile or uvfile handler
"emits the handler's fossilized error", and §10.1 says an error card's TEXT is
fossilized. Fossil does not fossilize these three: it URL-encodes them, so
spaces arrive as + and other unsafe bytes as %XX. Every other error card
Fossil sends is fossilized.
Both encodings yield a single token, so a receiver never misreads the card's
framing. The difference is in the text a person sees: a Fossil client
defossilizes what it receives, and shows a Fossil server's
malformed+file+line with the plus signs in it.
2. Location
§5.2, the paragraph beginning "After dispatch enters the file, cfile, or uvfile handler" (~line 1013):
…a validation failure reported by that handler instead resets accumulated response output, emits the handler's fossilized error, increments the request error count, and stops request-card parsing.
3. The divergence
Verified against Fossil 2.28 by source reading and by running it.
page_xfer() (src/xfer.c) sends the handler's accumulated error with %T
in all three places, at lines 1370 (file), 1391 (cfile) and 1405
(uvfile):
@ error %T(blob_str(&xfer.err))
In Fossil's printf, %T is etURLIZE (src/printf.c:162, "Make text safe for
HTTP"), while %F is etFOSSILIZE. The other error cards in the same function
use %F or are written pre-escaped, for example
@ error bad\scommand:\s%F(...) at line 1893.
A raw request through fossil http, as nobody with push permission, gives:
| Request card | Fossil 2.28 reply card |
|---|---|
file notahash 5 |
error malformed+file+line |
uvfile x/y 0 0 0 |
error malformed+uvfile+line |
The fossilized forms the draft describes would be error malformed\sfile\sline
and error malformed\suvfile\sline.
4. Impact
Cosmetic on the wire, visible to people.
- Framing. Unaffected: URL-encoding leaves no space in the text, so the card stays at two tokens and every receiver recognizes it as an error.
- Displayed text. A receiver following §10.1 defossilizes the text, which
turns
\sinto a space but leaves+and%XXas they are. Fossil-to-Fossil, the person pushing reads "malformed+file+line". - zit. zit's server follows the draft and fossilizes these errors
(
src/sync_test.zigpinserror malformed\sfile\sline), so a stock Fossil client reads zit's refusal with real spaces. zit's client receiving Fossil's form shows the plus signs, as Fossil's own client does. - Conformance testing. A test comparing a server's bytes against Fossil's will fail on these three cards whichever encoding the draft names, unless the divergence is recorded.
5. Correction
Keep the draft's rule. Fossilized TEXT is what §10.1 requires of every error card, it is what Fossil does everywhere else, and it is what a receiver decodes. Record Fossil's divergence where the handler path is described.
In §5.2, after "emits the handler's fossilized error,":
accumulated response output, emits the handler's fossilized error,
increments the request error count, and stops request-card parsing.
+ (Fossil instead URL-encodes the text of these three handler errors,
+ so spaces arrive as "+"; the card remains a single TEXT token, and a
+ receiver that defossilizes it, as Section 10.1 requires, shows the
+ "+" characters. Senders SHOULD fossilize, as for every other error
+ card.)
Alternative, which needs the operator's decision: propose that Fossil change
%T to %F at the three lines. That is a three-character patch to
src/xfer.c, and it would let the parenthetical be dropped. Filing it is a
separate decision from this erratum.
6. Rationale
- One encoding for every error card. §10.1 states a single rule. Making three cards an exception would give a receiver two decodings to choose between with nothing on the wire to tell them apart.
- The draft's rule is the readable one. Fossilized text defossilizes to the message as written; URL-encoded text does not, under the decoding §10.1 names.
- Recording it is enough. The divergence never breaks framing, so a note that tells implementers and conformance tests what to expect from Fossil covers it.
7. Verification
- Source: Fossil 2.28
src/xfer.c:1370,:1391,:1405(%T),:1893(%F, for contrast);src/printf.c:161-164(the conversion table). - Run: a
fossil newrepository withnobodygivengioz, sentpush SERVERCODE PROJECTCODEfollowed byfile notahash 5(and separatelyuvfile x/y 0 0 0) asapplication/x-fossil-debugthroughfossil http. The replies areerror malformed+file+lineanderror malformed+uvfile+line.
Prepared against draft-fossil-sync-protocol-00. The RFC remains the oracle;
the reference implementation is used only to confirm the observable behavior.