vouchfx Troubleshooting¶
This guide covers real failure modes, what they mean, and how to fix them.
Quick index: - Docker is not running or not reachable - Transient image pull corruption: "short read" or "unexpected EOF" - EnvironmentError: HealthGate timeout of 00:00:20 - Discovery root does not exist (dotnet run path resolution gotcha) - Understanding the four verdicts - Secret leakage in exception messages - Build fails with warnings-as-errors - Aspire topology timeouts or hangs - Steps run but assertions fail - Capture fails or placeholder is empty - Kafka messages not consumed (ordering or timing)
Docker is not running or not reachable¶
Symptom:
or
What it means: vouchfx orchestrates containers via Docker (Aspire + Testcontainers). If the Docker daemon is not running or not reachable, the topology cannot start.
Fix:
-
Check Docker is running:
You should see version and runtime information. If it fails, start Docker. -
On Windows / macOS: Start Docker Desktop.
-
On Linux: Ensure the
dockerddaemon is running: -
If Docker is running but still unreachable: Check permissions. On Linux, the current user must be in the
dockergroup: -
Verify the socket exists:
In CI: Ensure the CI runner has Docker available. GitHub Actions' ubuntu-latest includes Docker by default. For GitLab, use docker:dind service or a socket-bind mount (see README.md § CI integration with GitLab CI).
Transient image pull corruption: "short read" or "unexpected EOF"¶
Symptom:
ERROR: failed to build: failed to solve: failed to compute cache key:
short read: expected 12108405 bytes but got 11522155: unexpected EOF
docker pull / topology start failing with unexpected EOF or failed to register layer.
What it means: A layer download was corrupted mid-transfer — a registry or network hiccup, not a vouchfx or Dockerfile defect. When it happens during topology start (DCP's cold pull of a dependency image), vouchfx reports it as an EnvironmentError — per the verdict taxonomy, infrastructure breakage, never a test failure. When it happens while building your own application image, the build fails before vouchfx runs at all.
Fix:
- Retry the pull or build — a plain retry almost always succeeds; the corrupted partial layer is discarded and re-fetched.
- If it persists, pull the base image explicitly to isolate the failing layer: then check free disk space, and clear a corrupt build-cache entry with:
- In CI, re-run the job. To reduce exposure on cold runners, pre-warm the images the
suite needs (the reusable workflow's
prewarm-imagesinput does this; each pull is best-effort and non-fatal).
The Aspire orchestration component is not installed / could not be located¶
Symptom:
System.IO.FileNotFoundException: The Aspire orchestration component is not installed
at "/home/runner/.nuget/packages/aspire.hosting.orchestration.linux-x64/13.4.2/tools/dcp".
EnvironmentError: The Aspire DCP orchestration component could not be located. …
no 'aspire.hosting.orchestration.<rid>' version '13.4.2' package was found at '…'.
What it means: vouchfx orchestrates containers through .NET Aspire's DCP binary, which ships in the aspire.hosting.orchestration.<rid> NuGet package and is looked up in your per-user NuGet cache (NUGET_PACKAGES if set, otherwise ~/.nuget/packages/). The message means that package — at the exact pinned Aspire version and for your machine's platform — is not in your cache.
Fix: populate the cache once by restoring any project that carries Aspire.AppHost.Sdk at the pinned version, most simply this repository:
Version exactness matters: having restored some other Aspire version leaves a different version folder in the cache and does not resolve this error. The retired dotnet workload install aspire command does not help either — it installs Aspire 8.2.x packs outside the NuGet cache. If you keep DCP in a non-standard location, set the ASPIRE_DCP_PATH environment variable to the directory containing the dcp executable.
If the message shows a /home/runner/... path (first variant above): you are running vouchfx 1.0.0-alpha.5 or earlier from NuGet.org. Those pre-releases only consult a path baked in on the release build machine, so they fail on every other machine even with a fully populated cache. Fixed in 1.0.0-alpha.6 — upgrade (dotnet tool update --global vouchfx --prerelease), or build and run from source.
This failure is always classified as an EnvironmentError — infrastructure, not a product defect — so by default it does not break CI (see "Understanding the four verdicts" below).
For the full incident write-up — root cause, why the release pipeline missed it, and how regression is prevented — see the knowledge-base article KB: DCP orchestrator not found.
EnvironmentError: HealthGate timeout of 00:00:20¶
Symptom:
What it means: This timeout is not the vouchfx 120-second outer gate — it is an Aspire/DCP internal per-resource watchdog (approximately 20 seconds per resource). When a resource takes longer than 20 seconds to become healthy (e.g., a large image pull on a cold cache, or a slow container startup), DCP's watchdog expires.
This is NOT a vouchfx configuration knob (there is no --health-check-timeout flag). The 20-second window is built into Aspire/DCP and cannot be extended at runtime.
Fix:
-
Pre-warm Docker images locally before running the suite (most common fix):
Once an image is in the local Docker cache, DCP skips the pull and starts the container much faster. -
In CI, use the
prewarm-imagesworkflow input (GitHub Actions):
Or for GitLab CI, set VOUCHFX_PREWARM_IMAGES:
- Check container startup performance locally. If a container is consistently slow to start, it may have an expensive initialization step (e.g., schema migration, data load). Consider:
- Running the initialization ahead of time (in the container image build, not at startup).
- Using a healthcheck endpoint that returns quickly (not one that validates database connectivity, which adds latency).
-
Profiling the container with
docker logsto see where time is spent. -
Serialize topology startup if you are running tests in parallel locally. The
[assembly: CollectionBehavior(DisableTestParallelization=true)]attribute (on xUnit test projects that use Aspire) serialises test startup, preventing concurrent DCP resource startup from overwhelming the machine and triggering timeouts. This is already applied in the vouchfx test projects (seeVouchfx.Engine.Runtime.Testsfor the example). -
In CI, increase runner capacity or use a faster image pull network. If your CI runner has constrained network or disk bandwidth, image pulls take longer. Use a faster runner if available, or pre-cache the image in the runner's local Docker registry.
Why this happens: Aspire's Distributed Cloud Provisioning (DCP) watches each resource and gives it ~20 seconds to become healthy. This is a safety net to prevent tests from hanging indefinitely. If multiple resources start concurrently, they compete for I/O (disk, network), and a large image pull can exceed 20 seconds. Pre-warming the cache is the most reliable fix.
Discovery root does not exist (dotnet run path resolution gotcha)¶
Symptom:
when running:
What it means:
This gotcha is specific to running the CLI from source via dotnet run --project. When you use dotnet run, it changes the working directory to the project directory (src/Cli/Vouchfx.Cli/), not your current directory. So a relative path like ./tests/e2e resolves relative to that project directory, not your repository root, and fails.
If you are using the packaged vouchfx CLI (installed via dotnet tool install), you will not encounter this issue — the tool runs from your current working directory, so relative paths work as expected.
Fix:
Use one of these approaches:
- Use the packaged vouchfx CLI (recommended):
The packaged tool runs from your current directory, so relative paths behave as expected and this gotcha never applies.
- If you are contributing to vouchfx from source, build the CLI once and run the binary directly:
(On Windows:
# Build once dotnet build src/Cli/Vouchfx.Cli/Vouchfx.Cli.csproj -c Release # Run the built binary src/Cli/Vouchfx.Cli/bin/Release/net8.0/vouchfx run ./tests/e2esrc\Cli\Vouchfx.Cli\bin\Release\net8.0\vouchfx.exe run .\tests\e2e)
The binary runs from your current directory, so relative paths work as expected.
- Pass an absolute path to
dotnet run: (On Windows:dotnet run --project src/Cli/Vouchfx.Cli/Vouchfx.Cli.csproj -- run $((Get-Location).Path)/tests/e2e)
Best practice: For running vouchfx test suites, use the packaged CLI — it is simpler, requires no path-resolution workarounds, and works on any machine with .NET 8 installed. If you are working on vouchfx itself from source, build the CLI once and run the binary directly — it is faster (skips compilation) and avoids dotnet run quirks altogether.
Understanding the four verdicts¶
vouchfx distinguishes four outcomes (see docs/01 §12.1 for the full taxonomy):
| Verdict | Meaning | Example | Default CI exit code |
|---|---|---|---|
| Pass | All assertions passed. | A test runs end-to-end and all steps succeed. | 0 (success) |
| Fail | An assertion failed — a genuine product defect. | expect: { status: 200 } but the API returned 500. |
1 (always breaks CI) |
| EnvironmentError | Infrastructure problem, not a product defect. | Docker daemon unreachable, image pull fails, seed SQL fails. | 0 by default; 3 if --fail-on-env-error |
| Inconclusive | The engine could not decide; the assertion may pass if retried. | A RETRY step's polling window expires; a capture expression fails to match. | 0 by default; 4 if --fail-on-inconclusive |
Why the distinction?
In microservices, infrastructure is often brittle. A test might fail not because the code is wrong, but because Docker is slow or a database is down. The vouchfx verdict taxonomy lets your CI system handle each case independently:
- Fail (1) — Your code broke. Fix it.
- EnvironmentError (0 or 3) — The infrastructure is in trouble. Page on-call or escalate to DevOps.
- Inconclusive (0 or 4) — The engine could not decide; maybe timing was just off. Investigate or re-run.
By default, only Fail breaks CI. This reduces false positives and keeps developers focused on real defects, not infrastructure flakiness.
Opt into stricter gating with flags:
# Fail breaks CI; environment errors do not (default)
vouchfx run ./tests
# Also break CI on environment errors
vouchfx run ./tests --fail-on-env-error
# Also break CI on inconclusive results
vouchfx run ./tests --fail-on-inconclusive
# Both
vouchfx run ./tests --fail-on-env-error --fail-on-inconclusive
Secret leakage in exception messages¶
Symptom:
A script.csharp step throws an exception with a secret value in the message. The exception message is recorded as an observation in the --events JSON Lines output, where it becomes visible.
Example:
- id: validate-token
type: script.csharp
code: |
var token = Vars.Secrets.Resolve("vault/api-token").Reveal();
if (!token.StartsWith("sk_test_"))
throw new Exception($"Invalid token: {token}"); // DANGER: reveals the token
What it means:
Unlike the terminal output and HTML report (which redact secret values), the --events JSON Lines stream persists raw observations verbatim. If a script throws an exception with a revealed secret in its message, that message becomes a raw observation in the event stream.
Fix:
-
Never embed resolved secret values in exception messages. Use the reference path or a generic error message instead:
-
The only deliberate escape hatch is
SecretString.Reveal(). Call it only at the moment you inject the value into a sink (e.g. an HTTP Authorization header), never write the revealed value back intoVarsor any logged/serialised structure: -
Understand the three tiers of secret protection (see
docs/01§17): - Tier 1: No bake into IL — The secret value is never compiled into the C# source or IL (verified by SecretResolutionPipelineTests).
- Tier 2: Redaction in output — Terminal output, HTML report, and JUnit XML redact secret values (shown as "(redacted)").
- Tier 3: Raw event stream —
--eventsJSON Lines persists observations verbatim; authors must ensure no exception messages leak secrets.
Best practice: Treat exception messages as user-visible; never embed secrets in them. The reproducibility envelope records the reference hash, not the value — use that for reproducibility without embedding secrets.
Build fails with warnings-as-errors¶
Symptom:
build.csproj : error : Treating warnings as errors.
CSC5001: warning CS8360: [details...] cannot have...
What it means:
The vouchfx codebase is compiled with /WarnAsError enabled (zero-warning policy). Any compiler warning (including #nullable, unused variable, etc.) is treated as a build error.
Fix:
- Address the root cause: Read the warning message and fix it. Common examples:
- Unused
usingstatement → remove it. - Null-reference warning → add a null check or
!operator. -
Unused variable → remove it or prefix with
_. -
If the warning is spurious or unavoidable: Suppress it locally with
#pragma: -
Run the format gate locally before pushing:
This catches formatting issues (and indirectly, some warnings) early.
Why the strict policy? Zero-warning builds improve code quality and make intentional (narrowly-scoped) suppressions stand out in code review.
Aspire topology timeouts or hangs¶
Symptom: Tests hang or timeout waiting for the topology to start. The Aspire dashboard (if enabled) shows resources stuck in a transitional state.
What it means: A service or dependency failed to start or became unhealthy. Common causes:
- A service depends on another service that is not declared. Aspire cannot auto-discover undeclared dependencies.
- A healthcheck endpoint is broken. vouchfx health-gates ports; if the port is open but the healthcheck fails, Aspire retries indefinitely.
- A container image does not exist or cannot be pulled.
docker pullfails silently in some Aspire configurations.
Fix:
-
Run with the
Then examine--eventsflag to inspect observations to see which resource is stuck:events.jsonlforstep-attemptrecords showing stuck health-check attempts or failed container starts. -
Verify all dependencies are declared in
environment.servicesandenvironment.dependencies. If a service tries to connect to a database not in the environment, the topology cannot satisfy it. -
Verify the healthcheck endpoint is fast and works. Test it manually:
If the healthcheck endpoint hangs or fails, the container will be marked unhealthy. -
Check image availability:
If the pull fails, the topology cannot start.
Steps run but assertions fail¶
Symptom: A step runs (e.g., an HTTP request succeeds with status 200) but the assertion fails:
What it means: The step executed successfully, but the response did not match the expected conditions. This is a Fail verdict — a genuine product defect, not an infrastructure problem.
Fix:
-
Review the assertion. Is it correct for the current test?
http.restasserts the status code only — to check a response body,capturethe value and assert it in a laterscript.csharpstep (or adb-assert). -
Examine the actual response. Run the step in isolation (if possible) and log the full response:
-
Check for state threading issues. If an earlier step's capture is empty, a placeholder substitution may fail:
-
Re-run the scenario with
The JSON Lines file contains every step observation; examine it for the actual response.--eventsto capture raw observations:
Capture fails or placeholder is empty¶
Symptom:
or a placeholder resolves to empty/null in a later step.
What it means: A capture expression (JSONPath or XPath) did not match the step result, or a placeholder references a non-existent variable.
Fix:
-
Verify the JSONPath is correct. Use a JSONPath tester (e.g., jsonpath.com) to test the expression against your response:
-
Check the response structure. The actual response may differ from what you expected:
-
Use optional captures if a field might not always be present:
-
Use
continueOnFailureif a capture is truly optional: -
Verify variable names match. Placeholders are case-sensitive:
Kafka messages not consumed (ordering or timing)¶
Symptom:
An mq-expect.kafka step fails to find a message that was published earlier, even with verifyMode: RETRY.
What it means: Common causes:
- Message was published before the consumer started listening. Kafka does not replay historical messages by default (unless
earliestis configured). - Topic does not exist. The message was published to a different topic.
- Key or match criteria are too strict. The message exists but does not match the filter.
- Timing issue. The publish step and expect step are running concurrently; the expect starts before the publish completes.
Fix:
-
Ensure the order: publish → expect. In your steps, publish first, then expect:
- id: publish-order-event type: mq-publish.kafka target: kafka topic: orders.created payload: | { "orderId": "123", "status": "new" } key: "123" # Later step (runs after publish completes) - id: expect-order-event type: mq-expect.kafka target: kafka topic: orders.created match: key: "123" verifyMode: RETRY timeout: 10s -
Use a consistent topic name. Verify the publish and expect steps reference the same topic:
-
Relax the match criteria temporarily to debug. Remove the
keyor other filters to see if the message exists: -
Use
verifyMode: RETRYwith a reasonabletimeout(e.g., 10–30 seconds): -
Check Kafka broker health. If the broker is slow or unhealthy, messages may be delayed:
-
Understand Kafka's offset management. By default, vouchfx's Kafka consumer seeks to the latest offset. If a publish step and an expect step both run in the same scenario, the consumer might miss the message if it subscribes before the message is published. Ensure publish runs first.
RabbitMQ: message not in queue (ownership, durability, publish-after-declare)¶
Symptom:
An mq-expect.rabbitmq step asserts on a queue but the message is not found, even though the publish step ran first.
What it means: Common causes:
- Queue does not exist. RabbitMQ routing requires the queue to exist before a message can be routed to it.
- Durability mismatch. The queue was declared as transient (auto-delete) in a previous test and was deleted when emptied.
- Wrong routing key. The routing key used in the publish step does not match the queue name or binding.
- Message was already consumed. Another consumer drained the queue before the assertion ran.
Fix:
-
Declare the queue explicitly in the environment. If your SUT declares the queue at startup, ensure the HTTP trigger step runs before the assertion:
steps: # Step 1: Trigger SUT to declare the queue - id: initialize-service type: http.rest target: rabbitmq-service method: POST path: /init expect: status: 200 # Step 2: Now the queue exists; publish a message - id: publish-message type: mq-publish.rabbitmq target: events-rmq routingKey: orders-notifications payload: '{"orderId":"123"}' # Step 3: Assert the message is in the queue - id: assert-message type: mq-expect.rabbitmq target: events-rmq queue: orders-notifications match: payloadContains: "123" verifyMode: RETRY timeout: 10s -
Use durable queues. In your SUT or topology setup, declare queues with the durable flag set to
true: -
Match the routing key to the queue name. When using the default exchange, the routing key must match the queue name exactly:
-
Use
verifyMode: RETRYto wait for eventual consistency:
NATS: Inconclusive verdict on mq-expect.nats (stream created after publish — lost message)¶
Symptom:
An mq-expect.nats step times out with an Inconclusive verdict, but the message was published in a preceding step.
What it means: NATS JetStream messages are only retained if the stream exists before they are published. If you publish to a subject before the stream is created, the message is lost and cannot be recovered. The step then polls indefinitely and times out.
Common scenario:
1. Publish step runs and tries to publish to orders.created.
2. The provider creates the stream on first use (lazy initialisation).
3. But if there is a race or ordering issue, the stream might not exist yet → message is lost.
4. Expect step runs later, creates the stream, and polls — but the message is gone.
Fix:
-
Ensure the stream exists before publishing. The simplest approach is to have an expect step run before the publish to warm up the stream:
steps: # Step 1: Warm up the stream (ensures it exists) - id: ensure-stream-exists type: mq-expect.nats target: nats-broker subject: orders.created match: payloadContains: "dummy" # A dummy criterion that will fail continueOnFailure: true # We expect this to fail; we just want the stream created # Step 2: Now publish (stream is guaranteed to exist) - id: publish-order-event type: mq-publish.nats target: nats-broker subject: orders.created payload: '{"orderId":"123"}' # Step 3: Assert (stream exists and message is retained) - id: assert-order-event type: mq-expect.nats target: nats-broker subject: orders.created match: payloadContains: "123" verifyMode: RETRY timeout: 10s -
Use explicit stream names to avoid derivation ambiguity. When two subjects might derive to the same stream name, use explicit names:
-
Understand stream name derivation. Subject names are converted to stream names by uppercasing and replacing non-alphanumeric characters (except
-) with underscores. For example: orders.created→ORDERS_CREATEDorders_created→ORDERS_CREATED(collision!)orders-created→ORDERS-CREATED(different fromorders.created)
When in doubt, use explicit stream names in both publish and expect steps.
- Use separate NATS dependencies per scenario. If you have multiple scenarios asserting on the same subject, give each scenario its own
natsdependency to avoid cross-scenario message bleed:
Azure Service Bus: entity not declared in dependency config → EnvironmentError¶
Symptom:
What it means:
Azure Service Bus Emulator (and real Azure Service Bus) requires queue and topic declarations to be made explicit in the test's environment.dependencies section. Queues and topics that are not declared will not be created, and messages sent to them will fail with an EnvironmentError.
Fix:
-
Declare all queues and topics in the environment. List them explicitly under the
azureservicebusdependency: -
Match queue and topic names exactly. Ensure the
mq-publishandmq-expectsteps reference the declared names: -
For topic subscriptions, declare both the topic and subscription. A subscription cannot be created without its parent topic:
-
Understand the entity declaration syntax.
- Queues are simple strings:
queues: [queue1, queue2, ...] - Topics are objects with a
nameand optionalsubscriptionsarray:topics: [{name: topic1, subscriptions: [sub1, sub2]}]
Redis: AuthenticationError from the SUT (missing password credential)¶
Symptom: Your system under test fails with an authentication error when connecting to Redis:
or
What it means: The managed Redis dependency (provided by Aspire/Testcontainers) has authentication enabled, and your SUT is either: 1. Not providing the password at all. 2. Using the wrong password or username.
Fix:
- Inject the Redis password via environment variable. The engine stages the Redis connection string (including password) as
conn::<target>. Inject it into your SUT:
Your SUT should then parse this connection string and use it to connect.
-
If your SUT requires host, port, and password separately, extract them:
-
For .NET applications using StackExchange.Redis, construct the connection options from the credentials:
-
If the error persists, verify the Redis container is healthy:
SQL Server: slow cold start; EnvironmentError on first pull¶
Symptom:
The SQL Server container takes a long time to start for the first time.
What it means: SQL Server is a large image (multiple gigabytes) and has a lengthy initialisation sequence. On the first run, Docker must pull the image from the registry, which can exceed the Aspire/DCP 20-second health check window.
Fix:
-
Pre-warm the SQL Server image locally before running the suite (most effective):
Once the image is in the local cache, subsequent container startups are much faster. -
In CI, use the
prewarm-imagesworkflow input (GitHub Actions): -
Ensure sufficient disk space and memory. SQL Server requires at least 2 GB RAM and adequate disk space. If your Docker daemon is resource-constrained, increase limits.
-
Understand the startup sequence. SQL Server's health check waits for the database engine to be ready, which involves filesystem initialisation. This is non-configurable; the 20-second Aspire window is a hard limit.
SMTP/Mailpit: mail not found (wrong target key — HTTP API vs. SMTP endpoint)¶
Symptom:
Your system sends an email via SMTP, but the mail-expect.smtp step finds no messages.
What it means: Common causes:
- Your SUT is not connecting to Mailpit. The SMTP endpoint is not injected or the SUT is ignoring it.
- Wrong environment variable names. The SUT expects different variable names than what the test provides.
- Mailpit HTTP API is not reachable. The step is querying the wrong endpoint.
Fix:
- Inject both the SMTP host and port separately. Mailpit exposes two endpoints:
- HTTP API for querying:
conn::mailpit(e.g.,http://localhost:8025) - SMTP server for sending:
svc::mailpit-smtpwith separate host and port
Ensure your SUT gets both:
environment:
services:
my-app:
image: myco/my-app:latest
env:
SMTP_HOST: ${conn:mail.host}
SMTP_PORT: ${conn:mail.port}
-
Verify the SUT is sending email. Add a debug HTTP step before the mail assertion to confirm the SUT is processing the request:
-
Use
verifyMode: RETRYto wait for SMTP delivery: -
Check Mailpit is running. Verify the container is healthy:
-
Understand match semantics. All criteria (to, subject-contains, body-contains) are AND-conjunctive:
If you are not finding a message, relax the criteria one by one to isolate the mismatch:
# First, check if ANY email exists to this address
match:
to: alice@example.com
# Then add subject filtering
match:
to: alice@example.com
subject-contains: "Welcome"
# Finally, add body filtering
match:
to: alice@example.com
subject-contains: "Welcome"
body-contains: "Thank you"
Cross-scenario state bleed on non-reset stores (DynamoDB, MinIO)¶
Symptom: When you run multiple scenarios sequentially on the same topology, data from one scenario is visible in the next scenario. This happens with datastores that are not automatically reset (DynamoDB and MinIO).
What it means: DynamoDB and MinIO are not cleared between sequential scenarios — any writes one scenario makes persist into the next. This is a deliberate limitation (out of scope). Other datastores (PostgreSQL, SQL Server, MySQL, MongoDB, Redis, Elasticsearch) are automatically reset between scenarios.
Solutions:
-
Run scenarios in parallel (preferred). Parallel execution isolates each scenario in its own topology, avoiding any cross-scenario state:
-
Add explicit cleanup steps. As the first step of each scenario, explicitly clean the datastore:
steps: # Scenario 2: start fresh - id: cleanup-dynamodb type: script.csharp description: Clear DynamoDB tables for this scenario. code: | var config = new Amazon.DynamoDBv2.AmazonDynamoDBConfig { ServiceURL = (string)Vars["conn::dynamo"] }; var client = new Amazon.DynamoDBv2.AmazonDynamoDBClient(config); try { // Delete items you wrote in the prior scenario (application-specific logic) } finally { client.Dispose(); }
Note:
script.csharpcontributes no compile references of its own — the AWS SDK types above compile only because a step from a provider that contributes them (such asdb-assert.dynamodb) appears somewhere in the same scenario. A scenario whose only DynamoDB interaction is this cleanup script will fail to compile; include at least onedb-assert.dynamodbstep, or perform the cleanup through your SUT's own API instead.
- Use scenario-scoped keys. Prefix your test data with a scenario identifier, then delete by prefix pattern rather than table-wide flush.
State reset failed (environment error naming a dependency)¶
Symptom: A scenario fails during the reset phase (between scenarios in a sequential suite) with an environment error message like:
What it means: The engine attempted to clear a dependency's state between scenarios (the reset runs after each scenario completes in a sequential multi-scenario suite), but the reset operation failed. This is an environment error — a failure of the test infrastructure, not a failure of the system under test. Possible causes:
- Store became unhealthy mid-suite — a container crashed, lost network connectivity, or hit a resource limit. Check Docker logs:
docker logs <container-id>. - SQL Server with exotic schemas — temporal (system-versioned) tables are handled, but Respawn may fail on other unusual patterns (cross-database foreign keys, graph tables); consult Respawn's documented limitations.
- MongoDB capped or time-series collections — these collections reject the document deletion the reset performs; remove them from the seeded data or use standard collections.
- Elasticsearch reporting per-document failures — a delete-by-query partially failed; check Elasticsearch logs and ensure the cluster has sufficient resources.
- Connection/authentication lost — the test infrastructure's credentials or network path to the store changed between scenarios.
Solutions:
-
Check the event stream for the full reset error message — it names the dependency and the specific failure stage (
init,create, orreset): -
Inspect container health — if a Docker container reset failed:
-
For MongoDB, remove capped and time-series collections from the test data or switch to standard collections.
-
For SQL Server, temporal tables are handled automatically; if the reset still fails, look for other exotic schema patterns (cross-database foreign keys, graph tables) and consult Respawn's documented limitations.
-
For Elasticsearch, ensure the cluster is healthy and has sufficient resources; check the cluster health status:
See also¶
- Recipes — Task-oriented examples for common scenarios.
- vouchfx-samples — Real-world sample applications and test suites demonstrating patterns.
- Common Patterns — Authoring patterns and step structure.
- Language Reference — Complete field reference for every step type.
- Technical Architecture Blueprint — How the system works (Aspire, Roslyn, memory model, verdict taxonomy, secrets).
- Project README — Building and running vouchfx, CLI reference, exit codes.