Core: Disallow setting main branch ref to a tag#16753
Open
rahulsmahadev wants to merge 2 commits into
Open
Conversation
The main-branch special case in TableMetadata.Builder#setRef sets
currentSnapshotId and appends to the snapshot log without checking the
ref type, so a tag named main can be committed (for example
createTag("main", id) on a table whose only snapshots are staged). The
spec requires main to always be a branch, and once such metadata is
written every subsequent write fails with "Cannot update branch: main
is a tag".
Reject the tag in setRef before any builder state is mutated.
Co-authored-by: Isaac
Contributor
Author
|
@szehon-ho can you help take an initial look and see if this change is meaningful |
szehon-ho
reviewed
Jun 12, 2026
Comment on lines
+1332
to
+1334
| "Cannot set %s to a tag: %s must be a branch", | ||
| SnapshotRef.MAIN_BRANCH, | ||
| SnapshotRef.MAIN_BRANCH); |
Member
There was a problem hiding this comment.
Minor: the message repeats main twice (it expands to Cannot set main to a tag: main must be a branch). Could drop the second arg to read a bit cleaner, while still making clear that main can be set, just as a branch:
Suggested change
| "Cannot set %s to a tag: %s must be a branch", | |
| SnapshotRef.MAIN_BRANCH, | |
| SnapshotRef.MAIN_BRANCH); | |
| "Cannot set %s to a tag, it must be a branch", | |
| SnapshotRef.MAIN_BRANCH); |
Non-blocking, and it keeps the same Cannot set %s to ... shape as the sibling unknown-snapshot check just above.
Contributor
Author
There was a problem hiding this comment.
makes sense, done
Co-authored-by: Isaac
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.
Summary
TableMetadata.Builder#setRefaccepts a tag ref namedmain: the main-branch special case setscurrentSnapshotIdand appends to the snapshot log without checking the ref type, andvalidateRefsonly verifies that main's snapshot id matchescurrent-snapshot-id— never its type. The spec requires main to be a branch (format/spec.md,refs: "There is always amainbranch reference pointing to thecurrent-snapshot-id").Once such metadata is committed the table can no longer be written: every subsequent commit fails in
setBranchSnapshotInternalwithCannot update branch: main is a tag— which also shows the builder already assumes main is a branch elsewhere.This is reachable on any table whose metadata has snapshots but no
mainref yet:newAppend().stageOnly().commit()(WAP) followed bymanageSnapshots().createTag("main", stagedSnapshotId).commit()CREATE OR REPLACE(replacement metadata drops refs but keeps snapshots)set-snapshot-refupdates applied throughTableMetadata.Builder(update requirements assert ref snapshot ids, not ref types)This change rejects the tag in
Builder#setRefwith aValidationExceptionbefore any builder state is mutated. The check is deliberately not added tovalidateRefsso that already-written metadata files containing this corruption can still be read (and repaired by committing a branch ref for main).Test plan
TestTableMetadata#testSetRefRejectsTagForMainBranch— builder-level:setRef("main", tag)throws; tag refs under any other name still workTestSnapshotManager#testCreateTagNamedMainFails— end-to-end repro viaManageSnapshotson a staged-only table; also asserts the failed commit did not create the main ref:iceberg-core:test --tests TestTableMetadata --tests TestSnapshotManagerplus spotless/checkstyle locallyThis pull request and its description were written by Isaac.