Skip to content

The lock file

.fastgrpc.lock records every protobuf field number ever assigned to your messages. It is the most important file in your repository after the source code itself, and it must be committed.

Why

Protobuf identifies fields by number, not name. The wire format for User(id=42, name="Ashesh") is bytes that say:

1: 42           ← field number 1
2: "Ashesh"     ← field number 2

The receiving client decodes by looking up field numbers in its compiled stub. If the client thinks field 2 is name but the server now thinks field 2 is email, the client reads the email value into the name slot. No error is raised. Your data is just wrong.

What goes wrong without the lock

You ship this on day 1:

@dataclass
class User:
    id: int      # auto-assigned field number 1
    name: str    # auto-assigned field number 2

A month later, you add an email field in the obvious spot:

@dataclass
class User:
    id: int      # field number 1
    email: str   # NEW field number 2 (collides with name)
    name: str    # SHIFTED to field number 3

Without the lock, field numbers are derived from declaration order. Now:

  • Server serializes User(id=42, email="x@y.com", name="Ashesh") as 1: 42, 2: "x@y.com", 3: "Ashesh"
  • Old client decodes:
    • field 1 → id = 42 ✓
    • field 2 → name = "x@y.com"emails appear as user names
    • field 3 → unknown, silently dropped

You find out via a customer support ticket.

What the lock does

.fastgrpc.lock is the authoritative record of every name → number mapping that has ever existed for each message:

[User]
id = 1
name = 2
email = 3      # new field — gets the next free number, not 2

When you delete a field, the number is tombstoned and never reused:

[User]
id = 1
name = 2

[User.removed]
email = 3      # number 3 is permanently retired

If you later add a phone field, it gets number 4 — never 3. This prevents wire bytes meant for the old email from being silently reinterpreted as phone by a stale client.

Workflow

  1. Commit the lock. It's a normal text file; it belongs in version control.
  2. Review changes in PRs. A diff that shows a field number changing should require justification.
  3. Never edit it by hand. Treat it like package-lock.json or a database migration history — let the tool manage it; you just review.
  4. If you intentionally need to change a number (e.g. rebuilding from scratch with no clients in production), delete the lock file and let it regenerate.

Comparison

File Purpose
package-lock.json, Cargo.lock, uv.lock Pin dependency versions for reproducible builds
Database migrations History of schema changes that must be replayed in order
.fastgrpc.lock History of protobuf field-number assignments that must remain stable

All three are committed; all three protect against the same class of bug — silent state divergence between deployments.

Why fastgrpc needs this more than hand-written proto

When you write .proto files by hand, you literally type int64 id = 1; — the field number is right there in front of you. You'd never accidentally renumber a field; the diff would jump out in code review.

fastgrpc's pitch is "you don't write proto, you write Python." That convenience trades away the visible field number. The lock file reintroduces the safety check that hand-written proto gave you for free, automatically.