Preserve undecodable archives instead of aborting the read - #72
Merged
Conversation
Apple adds Protobuf message types between Keynote releases, so a document
written by a newer Keynote than we have protos for will contain archives we
can't decode. Until now the first such archive raised, and because
IWAFile.from_buffer wraps that in a per-file ValueError, `ls`, `cat` and
`unpack` died on the whole document - typically at
Index/CalculationEngine.iwa, long before reaching any slide content.
Fall back to an UnknownArchive holding the raw payload, and warn once per
(file, message type). message_info.length delimits each archive
independently, so an undecodable one never desynchronises those that follow.
UnknownArchive writes its bytes back verbatim, so pack/unpack stays lossless
for data we can't interpret; it survives the YAML representation via a
base64 field. Callers who would rather fail loudly can promote the warning:
warnings.simplefilter("error", codec.UnknownArchiveWarning)
The same fallback covers a mapped type whose payload won't parse, which is
the other way a single bad archive could take down an entire document.
Fixes #60.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0179M4xvAKPGrgKpy4AeCsM7
This was referenced Aug 7, 2026
Merged
For a should_merge patch, `klass` is a functools.partial whose repr embeds the
whole message_info, so the warning ran to several lines of Protobuf dump:
Failed to deserialize functools.partial(<bound method ...>, type: 0
version: 65535
...
Name the message type instead, defensively enough to survive a class without a
DESCRIPTOR:
Failed to deserialize patch to KN.BuildArchive of length 17
(KeyError: 10000); preserving it verbatim. (in Index/Chart.iwa)
This is the exact failure reported in #54, where a chart's diff_field_path
points at a field number the base message's descriptor doesn't carry.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0179M4xvAKPGrgKpy4AeCsM7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A single archive we can't decode killed the entire document.
IWAArchiveSegment.from_bufferraisedNotImplementedError, andIWAFile.from_bufferwraps that in a per-fileValueError, sols,catandunpackall aborted — usually atIndex/CalculationEngine.iwa, which is read long before any slide content.That's the second half of what #70 asks for, and it's the whole of #60 ("parsing stopped for the first non-extractable item"). It matters independently of #71: Apple adds message types between Keynote releases, so there will always be a next unknown type.
Fix
Fall back to an
UnknownArchiveholding the raw payload, and warn once per(file, message type)pair.message_info.lengthdelimits each archive independently, so an undecodable archive never desynchronises the ones after it. The payload slice is now taken before the type lookup, so the cursor advances either way.UnknownArchive.SerializeToString()returns the original bytes, so pack/unpack stays lossless for data we can't interpret.Callers who'd rather fail loudly keep that option:
Verification
Simulated #70 by removing a type that
table.keyactually uses, then unpacking:Before this change that aborted with zero usable output.
tests/test_unknown_archives.pycovers: warn-not-raise, byte-for-byte payload preservation, YAML round-trip, the undecodable-payload path, promoting the warning to an error, and once-per-type deduplication. Full suite: 26 passed.Fixes #60.