Read Git pkt-lines and validate refnames safely
You will learn enough of Git's common wire format to inspect a trace without miscounting bytes, and you will validate reference names with the installed command before putting them into a script or protocol message. Allow about 15 minutes. The examples use Git 2.43.0, matching the installed gitprotocol-common(5) manpage and the local git binary.
The route
Jump straight to the step you need, or tick off Done means at the end.
This is a read-and-check workflow. It does not create branches, alter a repository or require elevated privileges. Keep the distinction clear: the protocol document describes bytes exchanged between Git programs, while git check-ref-format checks a name you may later use as a ref.
1. Confirm the Git version and the task
Start by recording the version whose behaviour you are testing:
$ git --version
git version 2.43.0
The local manpage is labelled Git 2.43.0 and defines rules shared by several over-the-wire protocols and file formats. It is not a command that reads a network stream for you. Use it as the format specification, then use tracing or a small parser when you need to inspect actual traffic.
Checkpoint
If the version differs, do not silently attach the local examples to another implementation. Check that version's documentation before relying on edge-case behaviour.
2. Validate a full refname before using it
A protocol refname is either HEAD or a name beginning with refs/. The normal hierarchy uses names such as refs/heads/main and refs/tags/v1.2.0. Validate the complete name, including its prefix:
$ git check-ref-format refs/heads/release/2026-q3
$ printf 'exit status: %s\n' "$?"
exit status: 0
$ git check-ref-format 'refs/heads/release..candidate'
$ printf 'exit status: %s\n' "$?"
exit status: 1
A zero status means the name passes this syntax check. A non-zero status means it must not be sent or created as that ref. The common rules reject control characters, spaces, ~, ^, :, ?, *, [, backslashes, consecutive dots, a trailing slash or dot, a trailing .lock, and @{. They also require at least one slash for a general refname.
Do not confuse this with checking whether the ref exists. A valid name can still be absent, point at a different object, or be rejected by server policy.
3. Check a branch shorthand separately
When input is a branch name rather than a complete refname, use the branch mode. It prints the normalised name on success:
$ git check-ref-format --branch 'release/2026-q3'
release/2026-q3
$ git check-ref-format --branch 'bad..name'
fatal: 'bad..name' is not a valid branch name
This mode is not identical to prepending refs/heads/ yourself. The branch rules include branch-specific restrictions, and the command may print a normalised result. Capture that output if the accepted spelling is what you intend to use:
$ branch_name='release/2026-q3'
$ checked_branch=$(git check-ref-format --branch "$branch_name") || {
> printf 'invalid branch name: %s\n' "$branch_name" >&2
> exit 1
> }
$ printf 'validated branch: %s\n' "$checked_branch"
validated branch: release/2026-q3
Quote the variable. An unquoted ref supplied by a user can be split by the shell or expanded as a pattern before Git sees it. Validation is not a substitute for authorisation: do not allow an otherwise valid name to control an administrative operation without checking who requested it.
4. Read the four-byte pkt-line length
Many Git protocol payloads are carried in pkt-lines. The first four bytes are hexadecimal and describe the total byte length of the packet, including those four length bytes. The remaining bytes are the payload. The length is not the number of visible characters and is not merely the payload length.
These are the small examples from the format, written as visible text:
0006a\n total length 6, payload "a\n"
0005a total length 5, payload "a"
000bfoobar\n
total length 11, payload "foobar\n"
0004 total length 4, empty payload
The newline in 0006a\n is part of the payload and therefore part of the six-byte total. A receiver must accept a non-binary line with or without its trailing LF, treating the payload equivalently after stripping an LF when present. A pkt-line may also contain binary data, so a parser must handle bytes, not assume text encoding.
Checkpoint
For a packet beginning 000b, subtract four and read seven payload bytes. If your parser reads eleven payload bytes, it has counted the header twice.
5. Keep empty and flush packets distinct
0004 is an empty data pkt-line. 0000 is a flush-pkt, a control marker that ends a section and must be handled differently. Do not turn both into the same empty string in a parser:
0004 data packet with zero payload
0000 flush-pkt, section boundary
The specification says implementations should not normally send 0004, but receivers still need to distinguish it from a flush. A parser that treats every zero-length payload as end-of-section can stop early when it receives an empty data packet.
The maximum payload is 65,516 bytes, so the largest permitted total pkt-line length is 65,520 bytes, represented by the hexadecimal length fff0. Reject a length above that limit before allocating a buffer. This is both a protocol check and a resource-exhaustion boundary for a long-running service.
6. Investigate a real exchange without changing state
When a Git operation behaves unexpectedly, capture packet diagnostics around a read-only operation such as listing refs. The exact trace depends on the transport and negotiated protocol, so treat it as evidence rather than a fixed transcript:
$ GIT_TRACE_PACKET=1 git ls-remote https://git.example.invalid/TEAM/PROJECT.git
fatal: unable to access 'https://git.example.invalid/TEAM/PROJECT.git/': Could not resolve host: git.example.invalid
The address above is deliberately a placeholder and will fail. Replace it only with a repository you are authorised to inspect. Do not paste credentials into the URL. Packet traces can expose repository names, capabilities and other request details, so store them with the same care as other operational logs.
git ls-remote reads advertised references and does not update your local refs. It still contacts a server, may invoke credentials, and may be disallowed by a network policy. If you need a completely offline test, validate names with git check-ref-format and feed known byte sequences to your own parser instead.
7. Recover from the common mistakes
- If a name fails validation, preserve the original input for an error message, but do not retry by stripping punctuation or replacing characters automatically. That can send an operation to a different ref.
- If a trace appears truncated, check whether your reader treated
0000as ordinary data or confused0004with a flush. Then verify the four-byte length against the actual byte count. - If a ref is syntactically valid but cannot be fetched or pushed, inspect permissions, server policy and whether the ref exists. The common format rules do not grant access.
- If a diagnostic command unexpectedly starts an update, stop and review the command before retrying. The examples here use validation and ref listing only; no elevated privileges are needed.
Done means
- You recorded the installed Git version and checked version-specific documentation when needed.
- You validate complete refs with
git check-ref-formatand branch shorthands with--branch. - You count the four-byte pkt-line header as part of the total length.
- You distinguish an empty data packet,
0004, from a flush-pkt,0000. - Your parser accepts binary payloads, enforces the 65,520-byte total limit and does not allocate from an unchecked length.
- You keep packet traces and repository access within the authorisation and logging rules of the system you are diagnosing.