Skip to content

Known issues and carry-overs for later milestones

This file carries only problems that still exist. An entry that gets fixed is deleted, and the account of what it was and how it was found lives in the commit that removed it; git log -p docs/reference/known-issues.md is where to look for one. A closed entry left standing with a note saying it is closed costs a reader the same attention as a live one, which is the whole reason for the rule.

Things that are not open problems live elsewhere, and on 2026-08-27 four of them moved out of this file to where they belong. upgrading.md carries what rolls a fleet when an installation crosses a release, and release-notes.md carries what strands an object, which is real work for whoever is upgrading one, and nothing at all for anyone else. ca-rotation.md carries the CA rotation procedure, which is a thing a human drives rather than a thing that is wrong. That includes that nothing schedules one, which is a decision and not an omission, and where the clock is published so that nobody has to remember. persistent-storage.md carries what an operator owns about a persistent group's claims: that this operator never deletes one, that deleting one deletes a world, and how long a group whose storage is broken takes to say so. network-boundaries.md carries what the NetworkPolicy objects buy and what they do not, and what bounds the number of agents that may reach the operator, as measured scope rather than a list of faults. Installing the operator carries the manual grant a chart cannot make for a namespace that does not exist yet, and Chart values why the digest checked in at any tag describes the release before it.

Two of the things this file used to carry were facts about one cluster rather than about this code, and they now live where that cluster is described: the GitOps repository, beside the HelmRelease whose arguments they are about. Anything here should be a claim about this repository; a claim about paulwtf belongs to paulwtf.

Older documents in docs/ name sections of this file that no longer exist. That is the rule above working, not rot: a handover or a runbook records what was open at its milestone, and rewriting one to match today would falsify a record. git log -p docs/reference/known-issues.md is where a named section went.

The design decisions live in superpowers/specs/2026-08-07-minecraft-cloud-operator-design.md, in superpowers/specs/2026-08-08-agent-channel-design.md, in superpowers/specs/2026-08-09-paper-agent-design.md, in superpowers/specs/2026-08-10-proxy-channel-design.md, in superpowers/specs/2026-08-10-velocity-image-design.md and in superpowers/specs/2026-08-11-velocity-agent-design.md.

TestARecreatedOrdinalCreatesItsPodOnceThePredecessorIsGone failed once and was never reproduced

The test (internal/controller/server_controller_test.go) recreates an ordinal over a pod that is still terminating, lets the pod finish the way a kubelet would, and expects the next pass to create the successor. It failed one make test with status.podName empty, passed in isolation and on a full rerun, and nothing was captured.

One thing is ruled out rather than assumed: it is not cache lag. internal/testenv's client is client.New, a direct client with no informer behind it, so the hypothesis anyone reaches for first with envtest cannot be the mechanism.

Measured 2026-09-19 on paul-desktop, with 16 to 24 test binaries running the test in parallel beside full package runs: it did not recur in about 26,000 runs. The same runs found a sibling failure in the fixture, about once in 2,700: the API server decided the predecessor's delete on the pod as it was before its binding and removed it outright, shown by an audit log of the failing run. The fixture now holds the pod with a finalizer instead. If this entry's failure is the same stale read in the other direction, the controller reading the predecessor after the force delete, that would explain an empty status.podName with PodNameTerminating; it is not shown.

The assertion prints what a second occurrence needs and the first did not have: the Accepted condition, every pod in the namespace with its deletion timestamp and node, and whether the pod under the name is still the predecessor by UID. A lingering predecessor says the force delete did not take; a Server carrying PodNameTerminating with no such pod present says the controller decided against a pod that is no longer there; an empty namespace with a clean condition says something else refused the create. The second occurrence should be a diagnosis, and this entry leaves with it.

Voice relay: the first sender can still hold a player's reply path

The relay sends a player's voice replies to one UDP source per player. A UUID is public, so anyone who can reach the relay's UDP port can send for a player's UUID before that player starts voice, or right after a server switch rebuilds the bridge, and receive the replies meant for the player, who then hears nothing. The payload is encrypted, so nothing leaks; the damage is a denial of service for chosen players. A source silent for 3 seconds loses the path to any other.

Where the operator knows that both the game connection and the voice packets reach a proxy with the client's own source address, it tells the proxy so, and the relay then prefers a source on the game connection's host: that source takes the replies at once, and a source elsewhere can take them back only after 15 seconds of silence from it. That holds for a LoadBalancer with externalTrafficPolicy Local (the default), for NodePort and for HostPort, on the game path and on a voice override alike. HostPort rests on the CNI keeping the source address, which is not measured here. What remains:

  • A sender behind the same public IP as the player (a household, a campus, a CGNAT or VPN exit) is on the game connection's host too, so both match and the first sender wins.
  • Under a LoadBalancer with policy Cluster, behind a ClusterIP entry point, or behind anything else that rewrites source addresses, the operator does not tell the proxy, and the first sender wins.
  • A player whose game connection sits on another proxy replica gives the relay no address to compare. No Service sets session affinity, so with N replicas behind a LoadBalancer or NodePort a player's voice reaches the replica holding the game connection roughly one time in N if flows spread evenly (not measured); in a three-replica group most players get no preference. A voice announcement of the pod's own node (HostPort or an own-node override) sends voice to the replica's own node and covers most of this.