A platform engineer at a mid-size logistics company told me the moment Helm actually made sense to him wasn't reading the docs, it was watching a coworker delete a Tiller pod during an on-call incident in 2019 and realize half their cluster's deploy permissions lived in that one component. Helm 3 removed Tiller entirely a year later, and interviewers still use that history to check whether a candidate learned Helm as "the templating tool" or actually understands what problem it was built to solve. Helm bills itself as the package manager for Kubernetes, and the current stable line documents install, upgrade, and rollback as first-class operations against a tracked release object, not just a one-shot manifest apply (Helm documentation, Using Helm).
Here's the split that actually shows up in interviews: junior candidates can describe a chart as "a folder with YAML in it," and that's not wrong, but it stops them cold the moment someone asks why a chart dependency's condition flag isn't firing, or why a hook resource is still sitting in the cluster three releases later. The people who've actually operated Helm in production know that a release is a stateful object with its own revision history, that upgrades in Helm 3 use a three-way merge against the live cluster state instead of just diffing two manifests, and that hooks are explicitly not tracked as part of the release once they finish running. Those are the details this page leans on.
The questions below cover eight areas: what Helm actually is and how charts and releases relate, chart file structure and Chart.yaml, the template engine and values precedence, dependencies and subcharts, the release lifecycle (install, upgrade, rollback, uninstall), hooks, the Helm 2 to Helm 3 architecture change, and testing plus production practices. Code examples use real Helm chart syntax, not simplified pseudocode.
What Helm is, and how charts, releases, and repositories fit together
Almost every Helm interview opens here, and a candidate who's only ever run helm install from a copy-pasted command tends to get exposed within the first two questions.
Easy questions
17kubectl apply pushes whatever manifests you point it at, with no memory of what was deployed before, no built-in way to parameterize the same manifest for three different environments, and no single command to roll the whole set back together. Helm packages a set of related Kubernetes manifests into a chart, templates them so the same chart can produce different output per environment, and tracks every install and upgrade as a numbered release so you can inspect or roll back the entire release as one unit instead of hunting down which of twelve manifests changed.
The mental model that tends to click for people: Helm is closer to apt or npm for Kubernetes objects than it is to a YAML templating engine. The templating is the mechanism, the release tracking is the actual point.
A chart is a directory (or a packaged.tgz of that directory) containing Chart.yaml for metadata, a values.yaml for default configuration, a templates/ directory of Go template files that render into Kubernetes manifests, and optionally a charts/ directory holding dependency subcharts. Nothing about a chart is Kubernetes-specific magic, it's a convention over a directory layout that helm template and helm install know how to process.
A release is a specific instance of a chart installed into a cluster, identified by a release name and a namespace. Every install and every subsequent upgrade creates a new revision of that same release, and Helm stores the rendered manifests and metadata for each revision (as a Secret by default since Helm 3) so it can diff, inspect, or roll back to any prior revision on demand.
helm install checkout-api./charts/checkout-api -n payments
helm upgrade checkout-api./charts/checkout-api -n payments --set image.tag=1.4.2
helm history checkout-api -n payments
# REVISION UPDATED STATUS CHART APP VERSION
# 1 ... superseded checkout-api-1.0.0 1.4.1
# 2 ... deployed checkout-api-1.0.0 1.4.2A chart repository is just an HTTP server exposing an index.yaml plus packaged chart.tgz files, following a format Helm's client knows how to parse. Helm 3 ships with no default repository configured, you add one explicitly with helm repo add. Artifact Hub is a central, searchable catalog of charts published across many independent repositories, it's a discovery layer, not itself a chart repository that Helm installs directly from.
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm search repo bitnami/postgresql
helm search hub redishelm show values
Chart.yaml holds metadata, values.yaml holds default configuration, templates/ holds the Go template files that render into Kubernetes manifests, and charts/ holds any dependency subcharts pulled in via helm dependency update. Optional additions include a crds/ folder for Custom Resource Definitions installed before anything else, values.schema.json for validating user-supplied values, templates/NOTES.txt for post-install instructions printed to the terminal, and a templates/_helpers.tpl for shared, reusable template snippets (Helm documentation, Charts).
apiVersion (v1 for Helm 2 style charts, v2 for Helm 3), name, and version, where version must follow SemVer 2 or be coercible into it. Everything else, description, keywords, maintainers, icon, appVersion, dependencies, is optional. As of Helm 3.3.2, additional undocumented fields are rejected outright rather than silently ignored, so custom metadata has to go under annotations instead of being bolted on as an arbitrary top-level key.
apiVersion: v2
name: checkout-api
description: Checkout service Helm chart
type: application
version: 1.4.0
appVersion: "1.4.2"Helm templates are Go's text/template syntax, {{ }} delimiters, pipelines, control structures like if and range, extended with more than 50 functions from the Sprig library plus a handful of Helm-specific ones. default, quote, nindent, upper, and most of the string and math helpers you'll see in real charts come from Sprig, not from Go's standard template package.
replicas: {{.Values.replicaCount | default 1 }}
image: "{{.Values.image.repository }}:{{.Values.image.tag | default.Chart.AppVersion }}"These are predefined objects Helm injects into every template's rendering context without any chart author having to declare them. Release carries information about the current release, Release.Name, Release.Namespace, Release.IsUpgrade, Release.IsInstall. Chart exposes the parsed contents of Chart.yaml, so.Chart.Name and.Chart.Version are always available. Values is the fully merged configuration, chart defaults overridden by whatever the user supplied through -f or --set, accessed as.Values.whatever.key (Helm documentation, Charts).
Helm 3 consolidated dependency declarations directly into Chart.yaml under a dependencies: list, each entry specifying name, version, and repository. The separate requirements.yaml file that Helm 2 required alongside Chart.yaml is gone, everything a chart needs to describe its dependency tree now lives in one file (Helm documentation, Charts).
dependencies:
- name: postgresql
version: "13.2.0"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabledIt reads the dependencies list in Chart.yaml, fetches the matching chart versions from the declared repositories, and drops the packaged.tgz files into the charts/ directory, also writing or updating Chart.lock to pin the exact resolved versions. Skip it and helm install or helm template on a chart with declared but unfetched dependencies fails outright, Helm won't silently ignore a dependency it can't find locally.
By default, Helm 3 deletes the release record entirely along with the Kubernetes objects it created, so helm history on that release name returns nothing afterward and the name is immediately reusable. Passing --keep-history preserves the release's metadata (visible via helm status or helm history) even though the underlying resources are gone, useful for audit purposes or if you want helm rollback to still work to bring a previously uninstalled release back.
helm test executes any manifest annotated with helm.sh/hook: test against an already-installed release, typically a Job that checks the application actually works, hits a health endpoint, runs a smoke query against a database. Unlike pre-install or post-upgrade hooks, test hooks don't run automatically as part of install or upgrade, they only run when someone explicitly invokes helm test
Chart.yaml's dependencies: field replaced the separate requirements.yaml file Helm 2 required. Having dependency declarations split across two files was mostly a maintenance annoyance, easy to update one and forget the other stayed in sync, so Helm 3 consolidated everything about a chart's identity and dependency graph into a single file.
helm delete became helm uninstall, and helm fetch became helm pull, among other renames. It matters practically because a CI pipeline or a chart's README written for Helm 2 will reference commands that either don't exist in Helm 3 or behave slightly differently, so migrating a legacy pipeline isn't purely a version bump, it's worth actually reading the pipeline script rather than assuming the old commands still work.
helm lint checks a chart against Helm's built-in best-practice rules, missing icon, inconsistent Chart.yaml fields, obviously malformed YAML, without fully rendering every template against a real values set. helm template actually renders the chart's templates into final Kubernetes manifests using whatever values you supply, which catches logic errors, a typo in a.Values path, a broken range loop, that lint's static checks wouldn't necessarily surface.
Pass --version explicitly on install or upgrade, matching the exact SemVer string published in the repository's index. Skipping --version pulls whatever the repo currently marks as the newest version, which is fine for local experimentation but a real risk in a production pipeline, since a chart maintainer publishing a new version with a breaking values change could silently change what your next deploy does.
helm install checkout-api bitnami/postgresql --version 13.2.0Medium questions
23Both get merged into the final values passed to the templates, and --set wins on any key where the two overlap. The official docs put it plainly: --set values are merged into --values with higher precedence. If you pass multiple -f files, later files override earlier ones on conflicting keys, and multiple --set flags on the same command are applied left to right (Helm documentation, Using Helm).
helm upgrade checkout-api./checkout-api
-f values-prod.yaml
--set replicaCount=5
--set image.tag=1.4.2The order people get wrong in practice: they assume the last flag on the command line always wins across both types, but --set as a category outranks any -f file no matter where it sits in the argument list.
version tracks the chart itself, its templates, its structure, its values schema, following SemVer so Helm can reason about upgrade compatibility. appVersion documents which version of the actual application the chart currently deploys, purely informational, Helm doesn't parse it or enforce any format on it, and bumping appVersion alone doesn't require bumping the chart version, though most teams do both together for clarity.
A library chart, declared with type: library in Chart.yaml, exists only to hold reusable template snippets that other charts import, it produces no Kubernetes objects on its own and can't be installed directly. An application chart, type: application (the default), is a normal installable chart. Teams reach for library charts to share common patterns, standard label sets, a common Deployment template shape, across many microservice charts without copy-pasting the same _helpers.tpl into every repo.
_helpers.tpl holds named template definitions, blocks defined with {{- define "chartname.labels" -}}, that get invoked elsewhere with {{ include "chartname.labels". }}. The leading underscore tells Helm this file itself doesn't render into a Kubernetes manifest, files starting with an underscore or a dot are skipped when Helm walks templates/ looking for output to submit to the cluster, which is exactly what you want for a file that only defines reusable snippets.
{{- define "checkout-api.labels" -}}
app.kubernetes.io/name: {{.Chart.Name }}
app.kubernetes.io/instance: {{.Release.Name }}
{{- end }}values.schema.json lets a chart author enforce a JSON schema on whatever values a user supplies, catching a wrong type or a missing required field before templates even render. Without it, a typo like passing replicaCount as a string instead of a number might not fail until Kubernetes rejects the resulting manifest, or worse, might silently produce a broken object. Schema validation runs automatically during helm install, helm upgrade, helm template, and helm lint, so the failure shows up immediately with a clear message instead of surfacing downstream as a cluster-level error.
{{ template "name". }} renders a named template but can't be piped into another function, it dumps its output directly. {{ include "name". }} does the same rendering but returns the result as a string, which means it can be piped, most commonly into nindent or indent to fix whitespace alignment when the included block is dropped into an already-indented spot in the parent template.
metadata:
labels:
{{- include "checkout-api.labels". | nindent 4 }}A plain {{ action }} leaves the newline and any whitespace around it intact in the rendered output. {{- trims all whitespace, including the preceding newline, immediately before the action, and -}} does the same for whatever follows. Go templates left unchecked with control structures like if and range tend to leave behind blank lines and stray indentation in the rendered YAML, which either looks messy or, worse, produces invalid YAML if the indentation lands wrong. The hyphenated form is how chart authors keep rendered output clean without hand-editing whitespace after the fact.
helm template renders every manifest locally and prints the output, catching most syntax and logic errors without touching the cluster at all. helm install --dry-run --debug goes a step further, it actually contacts the API server to validate the rendered manifests against the cluster's schema (catching things like an unknown field or a bad apiVersion) while still not creating anything. helm lint checks a chart against Helm's own best-practice rules, missing required fields, inconsistent indentation, unused values, before either of the other two even run.
helm template checkout-api./checkout-api -f values-staging.yaml
helm install checkout-api./checkout-api --dry-run --debug -n staging
helm lint./checkout-apicondition points to a values.yaml boolean, dot-path notation, that determines whether that subchart gets installed at all, not just configured. Set postgresql.enabled: false in your parent chart's values and the entire postgresql subchart is skipped during rendering and install, useful for a chart that optionally bundles a database but expects most production users to point at an externally managed one instead.
A subchart is just any dependency chart pulled into charts/. An alias lets you declare the same dependency chart more than once under different names, useful when a parent chart needs, say, two independent Redis instances, one for caching and one for session storage, from the exact same upstream chart. Without alias, declaring the same chart name twice in dependencies would collide.
dependencies:
- name: redis
alias: redis-cache
version: "18.0.0"
repository: "https://charts.bitnami.com/bitnami"
- name: redis
alias: redis-sessions
version: "18.0.0"
repository: "https://charts.bitnami.com/bitnami"Helm loads the chart, merges the values (chart defaults, then any -f files, then any --set flags, in that precedence order), renders every template in templates/ with that merged context, runs any validation like values.schema.json checks, then submits the rendered manifests to the Kubernetes API server. It records the whole thing as revision 1 of a new release, storing the rendered manifests and metadata (as a Secret, by default, since Helm 3) in the release's namespace.
Rollback doesn't re-render the old chart version's templates fresh, it takes the already-rendered manifest snapshot Helm stored for that specific revision and applies it, creating a brand new revision in the release's history that happens to match the old content. So rolling back from revision 5 to revision 3 doesn't erase revisions 4 and 5, it adds a revision 6 whose content mirrors revision 3, which keeps the audit trail linear and honest about what actually happened over time.
helm rollback checkout-api 3 -n payments
helm history checkout-api -n payments
# revision 6 now appears, content matches revision 3--atomic tells Helm to automatically roll the release back to its previous state if the upgrade fails partway through, rather than leaving it in a broken, half-applied condition that requires a manual helm rollback to fix. It's effectively --wait plus an automatic rollback on failure bundled into one flag, which is the sane default for any upgrade run through CI rather than by hand at a terminal.
A release name is how you refer to a specific installed instance of a chart in later commands, upgrade, rollback, uninstall. Helm 2 required release names to be globally unique across the entire cluster, which was a real annoyance for anyone running the same chart in multiple namespaces for different teams or environments. Helm 3 scopes release name uniqueness to the namespace, since release metadata is now stored in that same namespace rather than centrally, so checkout-api can exist as a release in both staging and production without a naming collision.
A hook is a regular Kubernetes manifest, most often a Job, annotated with helm.sh/hook to run at a specific point in a release's lifecycle instead of being deployed as a normal, tracked part of the release. The available hook points are pre-install, post-install, pre-delete, post-delete, pre-upgrade, post-upgrade, pre-rollback, post-rollback, and test (Helm documentation, Chart Hooks).
apiVersion: batch/v1
kind: Job
metadata:
name: {{.Release.Name }}-db-migrate
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "1"
spec:
template:
spec:
containers:
- name: migrate
image: checkout-api:{{.Values.image.tag }}
command: ["./migrate.sh"]
restartPolicy: NeverA pre-upgrade hook runs as a distinct, trackable step before the actual application Deployment gets its rolling update, which means the migration completes (or fails, blocking the upgrade) before any new application pods start expecting the new schema to exist. Baking migration logic into container startup risks multiple replicas racing to run the same migration concurrently during a rolling update, since Kubernetes doesn't guarantee only one pod starts at a time.
Weight decides execution order among multiple hooks registered for the same event, lower weight runs first, and weights must be written as quoted strings even though they're numeric. Without an explicit weight, Helm falls back to sorting by kind and then by name, which is rarely what you actually want if execution order matters, so any chart with more than one hook on the same event should set weights explicitly rather than rely on the fallback.
Tiller was an in-cluster server component in Helm 2 that actually executed installs and upgrades on the client's behalf, which meant Tiller itself needed broad, often cluster-admin-level RBAC permissions to do anything useful for anyone using it. That created a real security problem: any user or process that could reach Tiller inherited Tiller's own permissions, regardless of what that specific user was actually allowed to do in the cluster. Helm 3 removed Tiller entirely, the Helm client now talks straight to the Kubernetes API server using whatever credentials are in your kubeconfig, so permissions are evaluated per-user through normal Kubernetes RBAC instead of through one shared, over-privileged component (Helm documentation, Changes since Helm 2).
Helm 3 stores release records as Kubernetes Secrets by default, in the same namespace as the release itself. Helm 2 stored this data as ConfigMaps, typically in the tiller namespace, which meant sensitive values, database passwords passed through a chart's values, for instance, sat in plaintext in a ConfigMap accessible to anyone with read access to that namespace. Secrets support at-rest encryption when the cluster is configured for it, which ConfigMaps don't, closing an easy-to-miss exposure path.
Helm 2 required release names to be unique cluster-wide, a direct consequence of Tiller storing all release metadata centrally rather than per-namespace. Helm 3 scopes release names to the namespace they're installed in, since release records now live alongside the release itself, so two teams can both run a release named checkout-api in their own separate namespaces without either one colliding with the other.
Values passed straight through values.yaml or --set end up stored in plaintext inside the release Secret Helm creates, which is better than a ConfigMap but still readable by anyone with get access to Secrets in that namespace. Tools built around SOPS or sealed-secrets encrypt the sensitive values in the source repository itself and decrypt only at deploy time, so a database password never sits in plaintext in git history or in an unencrypted values file on someone's laptop, closing the gap that raw values.yaml leaves open.
Kubernetes API version mismatches show up constantly here, a chart using an apiVersion for an object, an Ingress, a PodDisruptionBudget, that was deprecated or removed in the target cluster's Kubernetes version fails outright even though the same chart and values installed cleanly on an older cluster. Helm's Capabilities.KubeVersion object exists specifically so charts can branch their template output based on the target cluster's actual version instead of hardcoding one apiVersion and assuming every cluster supports it.
Keep values.yaml as the chart's sane defaults, then maintain a thin per-environment override file, values-dev.yaml, values-staging.yaml, values-prod.yaml, that only sets the handful of keys that actually differ, replica counts, resource limits, image tags, ingress hosts. Layer it in with -f values-prod.yaml at deploy time, so each environment file stays small and reviewable, and a change to a shared default only needs to happen once in values.yaml rather than being copy-pasted into every environment's override.
Hard questions
9The layering, lowest to highest precedence: the subchart's own values.yaml first, then any values the parent chart's values.yaml declares under a key matching the subchart's name (or alias), then whatever the user passes at install or upgrade time via -f or --set on the top-level command. A subchart never sees the parent's global values.yaml keys directly except through the special global: block, which every chart in the dependency tree can read regardless of nesting depth.
# parent values.yaml
global:
imageRegistry: registry.example.com
postgresql:
auth:
database: checkoutThat postgresql: block overrides the postgresql subchart's own values.yaml for the auth.database key specifically, while global.imageRegistry is visible to postgresql's templates as.Values.global.imageRegistry without the parent needing to reference the subchart by name at all.
A range over an empty or nil list in Go templates simply produces zero iterations, it doesn't error, so the surrounding YAML structure (like an env: key with nothing under it) can end up syntactically empty or malformed depending on what's around the loop. The fix is usually wrapping the whole block in an if that checks the list is both defined and non-empty, using something like {{- if.Values.env }} before the range, so an absent or empty value skips the entire env: block rather than emitting an env: key with no children.
{{- if.Values.env }}
env:
{{- range.Values.env }}
- name: {{.name }}
value: {{.value | quote }}
{{- end }}
{{- end }}import-values maps specific values a child subchart exposes up into the parent chart's own values context, so the parent can reference them without the caller having to reach down through the subchart's namespaced key path. It's meant for cases where a subchart deliberately publishes certain computed or default values (declared under its own exports: section) that sibling subcharts or the parent's own templates need to consume directly, rather than every chart in the tree having to know each other's internal value structure.
helm status
Helm 3 computes upgrades using a three-way strategic merge patch, comparing the old chart's rendered manifest, the new chart's rendered manifest, and the live object's actual current state in the cluster, rather than just diffing old manifest against new manifest the way Helm 2 did. That third input matters because it catches drift, a field another controller or a manual kubectl edit changed outside of Helm, and avoids blindly clobbering it back to whatever the old chart revision had, or conversely, failing to apply a legitimate removal because the live state doesn't match what Helm expected.
before-hook-creation, the default, deletes any leftover resource from a previous run of that same hook right before launching the new one, which keeps repeated upgrades from colliding on a Job name that already exists. hook-succeeded removes the hook's resource immediately after it completes successfully, useful for a one-off migration Job you don't want cluttering kubectl get jobs output. hook-failed removes it only on failure, which you'd pick if you specifically want a successful hook's logs and object left behind for later inspection but don't want failed attempts piling up.
Hook resources are explicitly not tracked as part of the release once Helm confirms they reached a ready or completed state, that's stated directly in Helm's own hook documentation. Helm fires the hook, waits for it to finish, then walks away, it doesn't remember that object as belonging to the release the way it tracks a normal Deployment or Service. Without an explicit deletion policy annotation, that Job (and its pods) just sit there indefinitely, and uninstalling the release later won't clean them up either, since uninstall only removes resources Helm actually tracks.
There's no central server serializing requests the way Tiller effectively did, so Helm 3 relies on the release Secret's own state as the coordination point, an upgrade in progress puts the release into a pending-upgrade status, and a second concurrent upgrade attempt against a release already in a pending state fails rather than silently racing against the first. It's not full distributed locking, but it's enough to stop two upgrades from corrupting the same release's history, the practical answer for a team is simply not to run concurrent Helm operations against the same release from more than one CI job at a time.
helm template renders manifests locally with no awareness of the actual cluster it might eventually target, it doesn't validate against the live Kubernetes API's OpenAPI schema, the installed CRDs, or the cluster's actual Kubernetes version. helm install --dry-run does perform that server-side validation, which is why a chart can look perfectly formed under helm template and still fail at install time, a CRD the chart assumes exists isn't actually installed, or the targeted cluster's Kubernetes version doesn't support a field the chart uses.
How to prepare for a Helm interview
Skip the tutorial-level "here's Chart.yaml" review and actually build something with moving parts: a chart with a subchart dependency wired through condition, a pre-upgrade hook running a fake migration script, and values files for two different environments. Break it on purpose, comment out a required field, remove --version, install two conflicting release names in the same namespace, and read the actual error message Helm gives you rather than just fixing it and moving on. Those error messages are what interviewers are really testing when they ask "what would you do if X failed."
The Helm 2 to Helm 3 history keeps coming up in interviews not because anyone expects you to have run Tiller in production, plenty of strong candidates started their Kubernetes careers after it was already gone, but because explaining why it was removed forces you to articulate what Helm's security model is actually protecting against. An answer that just says "Tiller was removed for security" without saying what specifically it exposed reads as memorized, not understood.
Get the reps in before the real thing
Reciting hook deletion policies from memory is not the same as defending your answer when an interviewer changes the scenario mid-question and asks what breaks. LastRoundAI's mock interview mode runs live technical rounds with real-time follow-up questions in your browser, and the free plan includes 15 credits a month that reset monthly rather than piling up unused. Starter is $19/mo if a handful of sessions isn't enough runway.
Once your Helm and Kubernetes answers hold up under a follow-up, the slower part of the job hunt is usually getting in front of enough platform and DevOps roles that actually test this depth instead of treating Kubernetes as a checkbox on a resume. Auto-Apply queues tailored applications for your review, 10 a month on the free plan, up to 400 a month on the Ultimate plan, and nothing goes out until you approve it.
Questions about either product go to contact@lastroundai.com. That's the only inbox we check.
How this list was built
Worth being straight about where these questions come from, because plenty of pages in this category are not. The set was compiled from a research pass across official documentation, vendor release notes, published engineering writing and public discussion of hiring processes, then cross-checked against the current version of each technology so nothing here describes behaviour that has since changed.
What that means in practice: these are the questions the material supports as reasonable and current for this role, not a transcript of any one company's loop. We have not sat in on your interview and we are not going to claim we have. Treat the list as well-sourced preparation rather than a leaked question bank, and expect your panel to phrase things their own way.
If you spot something out of date, tell us at contact@lastroundai.com and we will fix it.
Frequently asked questions
What Helm topics come up most often?
Interviewers concentrate on the parts that cause production incidents rather than the parts that are pleasant to learn. Expect the fundamentals to be assumed and the follow-up questions to sit one layer below what a tutorial covers.
Do I need hands-on Helm experience to pass?
It shows quickly either way. Textbook answers hold up until the interviewer asks what you did when it broke, and that is usually the question that separates candidates. A small real project you can discuss honestly beats a longer list of familiarity claims.
Is Helm still worth learning in 2026?
For interview purposes the question is really whether the teams you are targeting use it, which is worth checking against their actual job postings rather than general popularity rankings. Where it is in use it tends to be deeply embedded and slow to replace.
Should I memorise Helm syntax for the interview?
Rarely worth it. Most interviewers care that you know what to reach for and why, and will not fail you for forgetting an exact flag. Being confidently wrong about behaviour costs far more than admitting you would check the documentation.
What is the most common mistake in Helm interviews?
Answering the question that was asked and stopping there. The strongest candidates add the trade-off or the failure mode without being prompted, which is what signals real use rather than revision.
