Glossary#

Every term the rest of these docs uses without stopping to explain it, grouped by where it comes from and sorted within each group. Definitions say what the thing is first and where it bites podbench second — several of these are ordinary Kubernetes or Linux vocabulary that happens to decide something load-bearing here.

Podbench’s own words#

agent#

The process podbench runs as PID 1 inside a debug container — podbench agent. It writes the sshd config, the host key and the authorized_keys at start-up, then idles and reaps orphans. Every one of its start-up steps is an ensure rather than a create, because a restarted container has a completely fresh filesystem.

blocker#

The named mechanism that denied ptrace. Several unrelated mechanisms refuse with the same EPERM — a missing CAP_SYS_PTRACE, Yama, seccomp and a mismatch between the seat’s and the target’s LSM labels (SELinux MCS categories, or AppArmor profiles) — so naming which one is the entire point of capreport.

burnt name#

A container name that can never be used again for the life of a pod. An ephemeral container cannot be removed, restarted or edited, so once one has been created — even if it immediately died, or was rejected by the kubelet — its name is spent. Podbench therefore allocates podbench-1, podbench-2, … and a failed rung takes a fresh name rather than retrying its own.

capability ladder#

The ordered list of rungs attach tries. It exists because a cluster that refuses the privileged option should still get a working editor rather than an error.

capreport#

The probe that runs inside the landed seat, on that node, and reports what debugging is actually possible — and, when it is not, which of the four ptrace blockers said no. Its exit code is its verdict: 0 live attach, 10 read-only, 15 launch-only, 20 nothing. Everything attach prints about capability comes from here, never from the spec that was submitted.

dev pod#

Iterate mode’s sacrificial clone of a running pod: same image, same volumes, same labels if you asked for them, but with the application container idled and a podbench sidecar added. It is a second copy of the workload, which is why the mode is unsafe for a singleton.

hotfix manifest#

Not a Kubernetes manifest. A JSON file — .podbench-hotfix.json — written at the root of the hotfix claim, recording what the fix was made against: the repo, the base commit, the base image and its digest, the venv’s interpreter version, and the commits since. A copy travels in a pod annotation so that hotfix status needs one get pods and no exec.

launch-only#

The capreport verdict for a seat that can debug processes it starts, but cannot inspect the target: /proc/<pid>/root, maps and environ are denied, and so is attach. It is a rung of its own because the two halves of it are independent — tracing your own descendant needs no permission at all, so podbench dbg --launch ./prog still gives breakpoints, run and backtraces on a pod whose own memory is shut. Reported as exit code 15. The name matters: called read-only, it sends you to a sysroot that will not open; called nothing, it hides the one inner loop that works. It is the ptrace-gated paths that are gone, not the whole of /proccmdline, status and fd need no permission and answer here as they do anywhere, which is why podbench pids still lists the target’s processes.

launcher#

The half of podbench that runs on your machine — the doctor, attach, vscode, ssh-config, status, list, dev and hotfix verbs. It shells out to kubectl rather than linking a Kubernetes client library, so authentication, contexts and exec credential plugins are inherited rather than reimplemented.

manifest#

A Kubernetes object as JSON or YAML. Podbench authors these itself for the dev pod rather than using kubectl debug --copy-to. See also hotfix manifest, which is a different thing with an unfortunately similar name.

mode#

One of the three ways in. Observe is the attach and vscode verbs, Iterate is dev, and Hotfix is hotfix. The design documents use the mode names and the CLI uses the verbs; they refer to the same things.

origin#

The pod a dev pod was cloned from, or the workload a hotfix is applied to. It is never modified by Iterate mode, and is recorded on the clone as the podbench.dev/origin annotation.

rung#

One step of the capability ladder, and the security context that goes with it. Full is runAsUser: 0 plus CAP_SYS_PTRACE. Degraded is the target’s own uid with all capabilities dropped. Seat is whatever the namespace will admit. There is no rung between full and degraded, because a capability added to a non-root container is a silent no-op.

attach reports the rung it measured, from the seat’s own /proc/self/status: the uid and gid the kernel gave the container and its CapEff. The rung it asked for is on the ladder lines, and the two can differ in both directions — a mutating webhook that strips capabilities.add leaves a root seat whose spec is indistinguishable from the degraded rung, and a stored spec carrying thirteen capabilities can belong to a container with none effective.

A measured degraded rung is the credential match and nothing else: the seat’s uid and gid equal the target’s, with no capability effective. Both numbers, because __ptrace_may_access() compares the three group ids as peers of the three user ids and denies on any one differing pair — which is why a 1000:0 seat beside a 1000:1000 target is the seat rung however well its uid matches, and why podbench lands a gid-corrected seat at all. The match includes a root seat beside a root target, which no PSS restricted namespace would have admitted — the label says what the seat can do, not what let it in. The authored contexts are the other half of the story and are described under PSS.

Where either gid could not be read, the rung is not measured rather than a rung: half a comparison earns no label, and the report names what was requested instead and says which of the two numbers was missing.

list and status report a measured rung too, and take it without an exec: the agent writes the four numbers into the container log once, at start-up, and those verbs recover them with kubectl logs — a read, so the cost does not scale with the number of seats in a namespace. A seat whose log cannot be read, or one older than that report, is not measured, with its requested rung on a row of its own.

A rung is still not a verdict. What a seat can do is measured by capreport, and status reports that measurement — or not probed, never the rung — beside each seat it lists.

seat#

A podbench container you can work in: an editor, a shell, git and a debugger, inside the cluster. An attach seat is an ephemeral container in the live pod; a dev seat is an ordinary sidecar in the clone. Both run the agent and both get the same ssh wiring.

seat identity#

Whether sshd inside a seat can resolve a login name for the uid the seat runs as. Without one, ssh is refused before a key is even looked at — see NSS. A seat whose uid the image already has an account for has one already; otherwise a live-pod seat writes its own record into /var/lib/extrausers/passwd, which the image ships world-writable so that a seat running as the target’s uid and gid can, needing no flag and no cooperation from the pod. A dev sidecar is given a projected /etc/passwd file instead and writes nothing. A seat that database will not serve — it ignores records below uid or gid 500 — falls back to /etc/passwd, which the image pre-seeds with a static record for every free uid under 500 so that no write is needed. Pinning the seat to group 0 to make that file writable is not offered: the gid is half of the ptrace credential match.

sidecar#

An ordinary container added beside the application in a dev pod. Unlike an ephemeral container it may declare resources, mount volumes with subPath, and carry a readiness probe — which is why Iterate mode is built on a clone rather than on the live pod.

singleton#

A workload where a second copy is not merely wasteful but wrong: it holds a device that accepts one connection, claims a name on the network, or takes a lock. An EPICS IOC is usually all three. attach and hotfix are safe for one; dev is not, because the clone is the second copy.

spike#

A time-boxed experiment run against a real cluster to test an assumption before building on it. S1 (the ssh transport), S2 (vscode-server in an ephemeral container), S3 (gdb against a distroless target), S4 (the Python relaunch loop) and S5 (the no-capability fallback) were the Phase 0 gate, and are collated in the Phase 0 gate report — which is where most of the non-obvious code here comes from, and which wins wherever it and the design brief disagree. S6 came later and records a route not taken: suspending an Argo CD-managed workload.

sysroot#

The target container’s filesystem as seen from the seat, at /proc/PID/root. gdb is pointed at it so that a distroless binary’s libraries and sources resolve, with no shared mount and no cooperation from the target.

Kubernetes#

Argo CD#

A GitOps controller. It stamps what it applied from git with the argocd.argoproj.io/instance label or argocd.argoproj.io/tracking-id annotation — on the workload, not on the pods it makes. Podbench detects only those two, deliberately: the tracking key is configurable and its default collides with an ordinary Helm label, so erring toward missing a detection is safer than refusing a mode that works.

claim#

A PersistentVolumeClaim: a request for storage that a pod mounts as a volume. Hotfix mode’s claim is mounted over the application’s venv path, which is what makes a fix survive a restart. Pod volumes are immutable after creation, so a claim can never be added to a running pod — hence the deploy-time cooperation that mode asks for.

CreateContainerConfigError#

The waiting reason the kubelet reports when it refuses a container the API server already accepted. It arrives seconds after a successful kubectl exit, and for an ephemeral container it leaves a burnt name behind. This is the second, asynchronous half of the two ways a debug container gets refused.

distroless#

An image containing an application and its runtime dependencies and nothing else — no shell, no package manager, often no libc utilities. You cannot exec into one usefully, which is exactly the case podbench exists for: the seat has the tools and reads the target’s filesystem through sysroot.

drift#

A live object differing from what git says it should be. Under a GitOps controller with self-heal on, anything podbench writes to a git-managed object is drift and gets reverted, usually within seconds and always without telling you.

EndpointSlice#

The object listing the pod addresses behind a Service. A pod joins it by matching the Service’s selector and being Ready, which is why a dev pod needs a readiness probe that follows your process — without one it joins the moment it starts, while nothing is listening.

ephemeral container#

A container added to an already-running pod, through the pods/ephemeralcontainers subresource. It is the mechanism attach is built on, and it has three properties that shape everything: it cannot be removed, restarted or edited (see burnt name); it may not declare resources at all; and it starts from the image every time, so nothing may live only in its writable layer.

eviction#

The kubelet removing a pod to reclaim a resource — most relevantly ephemeral storage. An attach seat shares the pod’s storage budget and cannot reserve its own, and a vscode-server plus extensions is a 1.1–1.3 GB working set, so the whole pod including the workload can be evicted.

GitOps#

Managing a cluster by reconciling it against git. Iterate mode refuses outright against a GitOps-managed workload, because a Service cutover is drift on a tracked object and self-heal reverts it with no error anywhere.

JSON patch#

A patch that applies an explicit list of operations — RFC 6902, kubectl patch --type=json. Podbench uses it to replace a Service selector, because a merge patch would union the two maps instead: the dev pod gets added without the original being removed, which is the opposite of a cutover and invisible until half the responses are stale.

k3s#

A lightweight, single-binary Kubernetes distribution. The six spikes ran against a real 6-node k3s cluster.

kind#

Kubernetes IN Docker — a cluster that runs inside containers on one machine. CI runs podbench’s end-to-end suite on it.

kubectl#

The Kubernetes CLI. Podbench shells out to it for everything, including the ssh transport, so that one kubeconfig — with its contexts, proxies and exec credential plugins — serves both the API calls and the data path.

kubelet#

The agent on each node that actually runs containers. It is the second, later voice in the two-channel refusal: the API server can accept a container the kubelet then rejects (see CreateContainerConfigError), and the two need separate handling because only the first can be caught by wrapping the API call.

liveness probe#

A periodic check the kubelet uses to decide whether to restart a container. A process stopped at a breakpoint stops answering it, and the kubelet cannot tell that from a hang — so on a probed pod a debugging session is on a timer, and attach prints the arithmetic before you set one.

merge patch#

A patch that unions map keys — RFC 7386, kubectl patch --type=merge. Right for adding the hotfix provenance annotations to whatever else an object carries; wrong for a Service selector, where JSON patch is used instead.

ownerReferences#

Metadata naming the object that created this one. Podbench walks them upward — pod → ReplicaSet → Deployment — to find the workload that carries the GitOps mark, and to find the pod template that hotfix annotations must go on.

pod-template-hash#

A label the Deployment controller puts on the pods of each ReplicaSet. Podbench deliberately strips it (and the other controller labels) from a dev pod while keeping the Service selector labels: that puts the clone in the EndpointSlice without making it a ReplicaSet member, which would otherwise get one of the two matching pods reaped.

PSA#
Pod Security Admission#

The built-in admission controller that enforces the Pod Security Standards on a namespace, configured with pod-security.kubernetes.io/enforce and friends. It is what refuses a CAP_SYS_PTRACE container synchronously, in kubectl’s stderr, and so what makes the capability ladder necessary. Its wording differs between levels, so podbench matches only the one stable fragment of the message.

PSS#
Pod Security Standards#

The three named policy levels Pod Security Admission enforces. Privileged is unrestricted; baseline blocks known escalations; restricted additionally requires non-root, drop: ["ALL"], allowPrivilegeEscalation: false and a RuntimeDefault seccomp profile. Podbench’s degraded and seat rungs are authored to be admissible under restricted. That is a fact about the context podbench writes, not about the seat that lands: a measured degraded rung is a uid and gid match with no capability, and a root seat matching a root target wears it having been admitted under nothing of the kind.

readiness probe#

A periodic check the kubelet uses to decide whether a pod should receive Service traffic. Failing one removes the pod’s address from service quietly — no restart count, nothing that survives afterwards — which is why it is the deadline that matters most when you stop a process in a debugger.

ReplicaSet#

The controller a Deployment creates to maintain a pod count. Podbench reads it while walking ownerReferences but never annotates it: the next rollout would discard the edit.

resize subresource#

pods/resize — the in-place change of a running container’s resource limits, kubectl patch pod --subresource resize. podbench vscode uses it to make headroom for the editor by default (--no-resize declines); attach --resize exposes it for a number you choose yourself. Never fatal: the raised limit lives on the pod alone, so a rollout, scale, image bump or eviction regenerates it away silently.

RWO#

ReadWriteOnce — a volume access mode allowing one node to mount the volume for writing. The hotfix claim is RWO, which is why that mode supports exactly one replica: a second either cannot schedule or, under ReadWriteMany, races on the same checkout.

selector#

The label query that decides which pods a Service sends traffic to. dev --cutover replaces one so the dev pod alone receives traffic, and records the original on the clone so teardown can restore it exactly.

self-heal#

A GitOps controller setting that reverts drift automatically rather than only reporting it.

Service#

The stable name and address in front of a set of pods. See selector and EndpointSlice. Podbench never joins one silently: a dev pod carrying the origin’s labels would take production traffic, so it is always behind an explicit flag.

shareProcessNamespace#

A pod-level setting making all containers share one PID namespace, so each can see the others’ processes. A dev pod sets it; an attach seat gets the same visibility by targeting one container instead. Under it, PID 1 is /pause rather than the workload, and pkill -f matches processes in every container — both traps podbench is written around.

startup probe#

A probe that must succeed before the liveness probe and readiness probe begin. While it is still running it is the only deadline in force, which is why podbench works out which probe actually applies rather than listing all three.

strategic merge patch#

Kubernetes’ own patch type, which knows that lists like containers are keyed by name rather than by position. Podbench uses it for the resize subresource: a JSON patch would address the container by index, and silently resize the wrong one if the spec changed underneath.

subPath#

A volumeMount field projecting one file or directory from inside a volume, rather than the whole thing. The API server forbids it on an ephemeral container, and refuses the entire request when it sees one — which is why an attach seat can never be given a /etc/passwd file, and a dev sidecar can.

Linux, and why ptrace says no#

/proc/PID/root#

A symlink to the root of another process’s mount namespace. Reading it gives the seat a complete view of the target container’s filesystem — rootfs and volumes — with no shared mount and no cooperation from the target. The bridge is one-directional: the seat can read the application’s filesystem, the application cannot see the seat’s, which removes several tempting workarounds.

ambient set#

The capability set that would let a non-root process keep a capability. No container runtime populates it, which is the whole reason capabilities.add: ["SYS_PTRACE"] beside a non-zero runAsUser is a silent no-op: the capability reaches the bounding set and nothing else, leaving CapEff zero. Podbench refuses to author that combination rather than ship a seat that looks privileged and behaves unprivileged.

AppArmor#

A Linux security module that confines processes by profile — one of the two podbench names, beside SELinux, as the LSM blocker. ptrace is denied between two different labels, returning the same EPERM as everything else, so the report compares the seat’s with the target’s rather than judging either alone. Which module owns the label is read from /sys: /proc/<pid>/attr/current is a slot they share.

bounding set#

The ceiling on the capabilities a process can ever hold. A capability here but not in CapEff has no effect at all — see ambient set.

CAP_SYS_PTRACE#

The capability that permits attaching a debugger to a process you do not own, and reading another container’s filesystem through sysroot. Podbench’s full rung asks for it; Pod Security Admission is the thing most likely to refuse.

CapEff#

The effective capability set, readable in /proc/PID/status. CapEff: 0 on a container that asked for CAP_SYS_PTRACE is the measured symptom of the ambient set problem.

cgroup#

The kernel’s resource-grouping mechanism. Its path in /proc/PID/cgroup contains the container’s runtime id, which is the only attribution of a process to its container that stays correct under shareProcessNamespace and with a second podbench session attached. Podbench injects the target’s id as PODBENCH_TARGET_CID and matches it as a substring, because the debug container’s own cgroup namespace makes the path it reads relative.

EADDRINUSE#

The error a bind gets when the address is taken. In the relaunch loop it can also appear when nothing is listening — see TIME_WAIT — so podbench says explicitly when that is what happened, rather than leaving you to suspect your code.

EPERM#

“Operation not permitted”. Every one of the four blockers returns it, which is why a report that only prints the errno is worth nothing.

LSM#

Linux Security Module — the kernel’s hook framework for access-control policies layered on top of the ordinary uid/gid rules. Yama, AppArmor and SELinux are all LSMs, and security_ptrace_access_check() is the last thing __ptrace_may_access() calls: a ptrace that satisfies credentials, dumpability and CAP_SYS_PTRACE can still be refused here, with the same EPERM as everything else. That is what denies PTRACE_MODE_READ at Diamond on a seat sharing the target’s uid — established by elimination, since every other check demonstrably passes (issue #52). An LSM refusal names nothing on the way out, which is why capreport reaching “unknown” is a real outcome rather than a defect.

NSS#

Name Service Switch — how a Linux process turns a uid into a login name, usually via /etc/passwd. sshd resolves the name a client offers through NSS before it looks at any key, so a seat running as a uid NSS cannot resolve refuses every login with Permission denied (publickey), a message naming nothing about identity. It also breaks ssh-keygen, which calls getpwuid() whatever it is asked to do.

PID 1#

The first process in a namespace, which inherits orphans and whose exit ends the container. The agent is PID 1 in a seat, which is why it never exits over a failed start-up step — and why Iterate mode idles the application container by authoring sleep infinity into the spec rather than killing the live PID 1, which the kubelet would simply restart with pristine image code.

ptrace#

The system call debuggers use to attach to and inspect a running process. Whether it is permitted is the single question the capability ladder and capreport exist to answer. Without CAP_SYS_PTRACE the kernel compares credentials, and __ptrace_may_access() compares six ids and not three — gid, egid and sgid are peers of uid, euid and suid, and one differing pair denies in both directions, measured. That is why a seat runs as the target’s group, why the report prints uid:gid for both sides, and why podbench lands a corrected seat when the measurement disagrees with what it authored.

reaping#

Collecting the exit status of terminated child processes so they do not accumulate as zombies. PID 1 inherits every orphan in the namespace, so the agent does it.

seccomp#

A kernel facility filtering which system calls a process may make. One of the four blockers: a profile can reject ptrace itself.

SO_REUSEPORT#

A socket option letting several processes bind the same port, with the kernel splitting incoming connections between them. It is why the relaunch loop refuses to start while anything is listening: a second bind succeeds with no error and traffic is served from old and new code at once, with nothing in any log to say so.

ss#

The socket listing tool, from iproute2. Podbench runs it twice per relaunch — once for listeners, once for TIME_WAIT — because ss -l cannot see the second.

TIME_WAIT#

The state a closed TCP socket sits in for up to about a minute. A port with sockets in it can refuse a rebind while ss -l shows no listener at all, so podbench warns rather than letting an EADDRINUSE look like a bug in your code.

Yama#

A Linux security module whose ptrace_scope setting restricts who may attach to whom: 0 is classic behaviour, 1 restricts attachment to descendants, 2 requires admin, 3 forbids it entirely. It is host-global and node-local — two nodes in one cluster can disagree by kernel flavour — which is why podbench measures it on the node it landed on and prints the node name, and why “it worked yesterday” is explicable. CAP_SYS_PTRACE overrides it.

Binaries and symbols#

These decide what a debugger can tell you once it has attached, and they fail in a way worth naming: “cannot read the file” and “the file carries no debug information” look alike from the editor and are nothing alike underneath. The first is a refusal, the second is a working session with addresses instead of source.

BFD#

The Binary File Descriptor library, part of GNU binutils — the layer that actually parses object files. gdb, objdump, ld, readelf and strip all read ELF through it rather than each implementing the format. So a BFD: line is the library complaining and not gdb, which matters when deciding what to upgrade: gdb’s ability to read a binary is its binutils’ ability. gdb renders BFD’s refusals in its own words, of which bad valuebfd_error_bad_value — is the one meaning “I parsed this far and the file does not make sense”.

build-id#

A hash the linker embeds in .note.gnu.build-id, identifying an exact build of a binary. It is what debuginfod looks a binary up by, so a stripped binary that kept its build-id can still be given symbols, and one without it cannot. Podbench reads it when deciding what to say about a target: “no .debug_info, but the build-id is present” and “no .debug_info and no build-id either” are different predictions about what you are about to see.

debuginfod#

A protocol and public service that serves DWARF for a build-id, so a stripped distro binary can be debugged without installing a -dbg package. Needs egress and ca-certificates, which is why --no-debuginfod exists. On Debian it serves symbols but not sources (report §3.2), so expect named frames and no source view.

DWARF#

The debug-information format, carried in .debug_* sections. It is what turns addresses into file, line, variable and type — everything a source-level debugger shows beyond a raw backtrace. Its absence is not an error: gdb attaches to a binary with no DWARF perfectly well and shows disassembly and whatever names the symbol tables hold.

ELF#

Executable and Linkable Format, the object-file format on Linux. Podbench parses just enough of it by hand — the machine, the section names — to decide which debugger flavour applies, because that decision must be made from the target’s own binary rather than from the node’s architecture or from anything the user typed.

stripped#

A binary whose symbol table, debug sections or both have been removed. Debuggable — breakpoints by address, frames from the dynamic symbol table, library names intact — just not at source level, unless debuginfod can serve the missing DWARF.

symbol versioning#

The scheme letting one shared library export several incompatible versions of a symbol, recorded in the .gnu.version_r and .gnu.version_d sections. Ordinarily invisible; it matters here because a BFD older than the toolchain that linked a binary can reject its .gnu.version_r outright, and then the file cannot be read at all. Measured on a Debian bookworm seat (binutils 2.40) against a RHEL-family target image: the target’s own application binaries read fine, its distro binaries — /usr/bin/bash among them — did not.

ssh, and the transport#

ControlMaster#

The ssh feature that multiplexes further sessions over one existing connection. Podbench’s generated stanza turns it on with a ControlPersist timeout, which is what makes a reconnect cost 0.058 s against 0.345 s cold.

CRI#

Container Runtime Interface — the API between the kubelet and the runtime (containerd, CRI-O). Its exec streaming is what carries podbench’s ssh connection, and its one sharp edge is fd 2.

fd 2#

File descriptor 2, standard error. Closing or replacing it in a kubectl exec’d process makes the CRI tear down the entire exec stream, truncating stdin and stdout mid-transfer with a zero exit code. This is why sshd is run with -e (which keeps fd 2 owned and open) and -o LogLevel=ERROR (which keeps it silent), and why no wrapper may redirect it.

HostKeyAlias#

The name ssh records a host key under, independent of the hostname. Podbench keys it on the pod UID, so a re-created pod appears as a new host rather than as a man-in-the-middle warning.

inetd mode#

sshd -i: sshd serving one connection on its own stdin and stdout instead of listening on a socket. It is what lets a kubectl exec channel be the ssh transport — no listening socket in the pod, no port-forward, no pod IP, and no inbound network path of any kind.

known_hosts#

The file ssh records host keys in. Podbench writes its own under ~/.podbench/ rather than touching yours, and the generated stanza points UserKnownHostsFile at it.

ProxyCommand#

An ssh option naming a command whose stdin and stdout carry the connection, in place of a TCP socket. Podbench’s is a kubectl exec running sshd in inetd mode, which is the whole network story: the outer authentication is your kubeconfig and the inner one is your ssh key.

Remote-SSH#

The VS Code extension that runs a server on a remote host and edits there over ssh. Pointing it at the generated host alias is how the editor gets into the cluster.

sftp#

The file-transfer subsystem of ssh. It works here like everything else, because the transport is a real ssh connection rather than an emulation of one.

sshd#

The OpenSSH server. Podbench runs it per-connection from the ProxyCommand, against a generated config file of its own rather than the image’s, so the working configuration is a reviewable artifact and the distro’s sshd is left alone.

vscode#

The laptop verb that lands an Observe-mode seat, sizes the pod against vscode-server’s measured footprint, provisions debugpy into the target and opens the editor on the seat’s home. Both mutations are on by default--no-resize and --no-provision decline them — which makes it the only verb that changes a running workload without being asked for a number. See resize subresource and mode.

vscode-server#

The server half of Remote-SSH, unpacked into the seat’s home on first connect. Measured at 1215 MiB in a seat carrying a real Remote-SSH session (2026-08-16) — the figure resize.EDITOR_HEADROOM checks this pod’s headroom against, and the one memory cost that still earns a warning. On disk a working session with extensions and a language-server index reaches 1.1–1.3 GB, which is the ephemeral-storage figure. The ~700 MiB once quoted was an S2 projection taken without a GUI client.

Python packaging#

.pth file#

A file in site-packages that Python’s site module processes at start-up. A line naming a directory is added to sys.path only if that directory exists — with no warning when it does not, which is why an install spanning two containers’ filesystems dangles silently and surfaces much later as an unrelated-looking ModuleNotFoundError.

debugpy#

The debug adapter for Python, used to attach VS Code’s debugger to a Python process. The native equivalent is gdb.

dist-info#

The metadata directory an install writes beside a package, recording its version, entry points and file list. An editable install bakes these in at install time, which is why changing pyproject.toml needs the installer run again while changing code does not.

editable install#

An install that points at your checkout instead of copying it, so edits take effect without reinstalling — pip install -e or uv pip install -e. It works by writing a .pth file into the interpreter’s site-packages, which is what makes it sensitive to the mount namespace the checkout lives in.

PEP 660#

The standard defining modern editable installs. Its exec-style .pth file prints a traceback and then carries on with exit 0 when the path it names does not exist — a failure loud enough to scroll past and quiet enough to miss.

pyvenv.cfg#

The file at the root of a venv recording which interpreter created it. In Hotfix mode it is on the claim, which makes it the one record of the interpreter that stays true across an image upgrade — so podbench reads the version from it and measures the live one separately.

uv#

The Python package and project manager podbench’s image ships. uv sync --frozen installs exactly what the lockfile says, because a dev pod that silently re-resolves dependencies is no longer running what production runs.

uvx#

uv tool run — fetch a tool and run it in one shot, leaving nothing installed. It is podbench’s canonical invocation: uvx podbench attach my-pod.

venv#

A Python virtual environment: an interpreter, a bin/ and a site-packages of its own. Hotfix mode mounts a claim over the application’s, which is what makes a fix outlive the container — and also what makes the venv shadow the image’s after an upgrade, since its bin/python is a symlink to an interpreter path inside the image.