Troubleshooting
Each error entry is headed by the message Trax raises, quoted as it appears in the source, with
the names it fills in shown as X, Y and Z. Search this page for a distinctive phrase from
the message you have. Problems that raise no error are at the end.
"Could not find train with input type (X)"
The TrainBus has no train registered for the input's type, and throws NoTrainForInputException. The message names the type, the assemblies the mediator scanned, and the two fixes:
Could not find train with input type (MyApp.Orders.OrderInput): no IServiceTrain<OrderInput, TOut> is registered for it. Scanned assemblies: [MyApp.Api]. Add a train that takes this input type, or add the assembly that holds its train to ScanAssemblies(...).Causes:
- The assembly containing your train is not in the scanned list, because it was never passed to
AddMediatororScanAssemblies - Your train doesn't implement
IServiceTrain<TIn, TOut> - Your train class is
abstract
Fix: add the train's assembly to the scan.
using Trax.Effect.Data.Postgres.Extensions;
using Trax.Effect.Extensions;
using Trax.Mediator.Extensions;
services.AddTrax(trax => trax
.AddEffects(effects => effects.UsePostgres(connectionString))
.AddMediator(mediator => mediator
.ScanAssemblies(typeof(Program).Assembly, typeof(YourTrain).Assembly))
);This is a host configuration error, reported to the host. The exception is not a TrainException, so Trax.Api and the scheduler's runner endpoints, which pass a TrainException's message through as a train's own words, do not pass this one through that way; read it in the host's log. A caller that asks ITrainExecutionService for a train name that does not exist gets TrainNotFoundException instead, whose message is always "The requested train was not found." and does not say what is registered.
The scheduler checks the same thing when a train is scheduled (Schedule, ScheduleMany, ScheduleOnceAsync and the rest) and throws InvalidOperationException with the same fix:
No train implements IServiceTrain<OrderInput, TOut>. Add the train's assembly to AddMediator(m => m.ScanAssemblies(typeof(MyTrain).Assembly)).A scheduled run runs the train it names, so the scheduler also refuses a train that was not scanned when another train takes the same input type: Train 'MyApp.Orders.IOrderTrain' is not registered, although another train takes OrderInput., followed by the same fix.
"AddTraxDashboard() requires AddTrax() to be called first"
AddTraxDashboard() checks, when it is called, that AddTrax() has already registered Trax in
the same IServiceCollection. The full message is "AddTraxDashboard() requires AddTrax() to be
called first. Call services.AddTrax(trax => ...) before services.AddTraxDashboard()."
Cause: AddTrax() was not called, or it was called after AddTraxDashboard().
Fix: Call AddTrax() first:
builder.Services.AddTrax(trax => trax
.AddEffects(effects => effects.UsePostgres(connectionString))
.AddMediator(typeof(Program).Assembly)
.AddScheduler()
);
builder.Services.AddTraxDashboard(o => o.RequireRoles("Admin")); // After AddTrax()"AddTraxGraphQL() requires AddTrax() to be called first"
The same check in AddTraxGraphQL(), with the same fix: call
services.AddTrax(trax => ...) before services.AddTraxGraphQL(...).
"AddTraxDashboard() has already been called on this host"
AddTraxDashboard() was called twice. A second call would replace the first one's authorization
settings, so a shared bootstrap that allows anonymous access could silently undo a host's
RequireRoles(...). Call it once, with every dashboard option in that call.
"Call AddEffects(...) before AddMediator(...)."
The step builder pattern enforces configuration ordering at compile time. AddMediator() is only available on TraxBuilderWithEffects (returned by AddEffects()), and AddScheduler() is only available on TraxBuilderWithMediator (returned by AddMediator()).
Cause: Calling methods out of order. Trax.Mediator and Trax.Scheduler report order mistakes as CS0619 with the fix as the text:
| Error text | Cause |
|---|---|
Call AddEffects(...) before AddMediator(...). | AddMediator() called before AddEffects() |
AddMediator(...) is already called. Call it once and configure everything in that call. | AddMediator() called twice |
Call AddStateMachines(...) before AddMediator(...). | AddStateMachines() called after AddMediator() |
Call AddMediator(...) before AddScheduler(...). | AddScheduler() called before AddMediator(), straight after AddEffects() or on the bare builder |
Call UsePostgres(...), UseSqlite(...) or UseInMemory(...) before AddDataContextLogging(...). | AddDataContextLogging() called before a data provider |
Fix: Follow the required order: AddEffects() -> AddStateMachines() if you use it -> AddMediator() -> AddScheduler():
services.AddTrax(trax => trax
.AddEffects(effects => effects.UsePostgres(connectionString)) // Step 1
.AddMediator(typeof(Program).Assembly) // Step 2
.AddScheduler() // Step 3
);"AddScheduler() requires a data provider (UsePostgres(), UseSqlite(), or UseInMemory())"
The scheduler's background services keep manifests, Metadata and work queue entries in the
database, so AddScheduler() refuses a host whose AddEffects(...) names no data provider.
Fix: add one, from its own package (Trax.Effect.Data.Postgres, .Sqlite or .InMemory):
services.AddTrax(trax => trax
.AddEffects(effects => effects.UsePostgres(connectionString)) // or UseSqlite(...) / UseInMemory()
.AddMediator(typeof(Program).Assembly)
.AddScheduler()
);"AddJunctionProgress() requires a data provider (UsePostgres(), UseSqlite(), or UseInMemory())"
Junction progress writes the current junction to the run's Metadata row and reads cancellation
requests from it, so it needs a data provider in the same AddEffects(...) call. Add one as above.
"AddStateMachines() requires a data provider"
The state-machine snapshot store keeps drafts and effect claims in the database. Configure a data
provider in AddEffects(...) before calling AddStateMachines(...).
"N of M registered trains cannot run:"
The host refused to start. The mediator's startup check builds every registered train and replays
its chain before the host serves traffic, and found trains that would fail every run. Each
indented line below the heading names one train and one problem, in the form
IMyTrain: step 2 (MyJunction) needs 'Customer' in Memory and nothing before it puts one there.
The entries that follow cover each kind of line. A train the check cannot build outside a request
is not refused: it is logged as a warning, "The chain of X was not verified at startup".
"X cannot be built: its constructor needs 'Y', which is not registered"
The host refused to start because a train's own constructor asks for a type that nothing registers
in the container, so the train would fail every run. Register the type before building the host.
The same refusal names a registered dependency whose own constructor needs an unregistered type,
with the path the train reaches it through: ProbeRepository needs 'IClock', which is not registered, and the train's constructor reaches it through 'IProbeRepository'. A train class with
no public constructor is refused as FooTrain has no public constructor. Give it one. A dependency
registered through a factory that can only build inside a request (one reading HttpContext, say)
is not refused: the train is skipped with a warning, "was not verified at startup".
"Junction 'X' (train 'Y') needs 'Z' as a constructor argument"
A junction's constructor asks for something the chain never produced and the container does not hold. Trax builds a junction through its single public constructor, taking each argument from Memory first and the container second, so the message names the junction, the train and the missing type.
Fix: Register the missing service, or chain a junction that outputs it before this one. On a
generic host the startup chain check finds this before the first run and refuses to start, with
step N (X) needs 'Z' as a constructor argument. The run-time message above is what a host that
skips the check (the Lambda runner, a bare ServiceProvider, SkipChainVerification()) sees.
"Junction 'X' (train 'Y') has 2 public constructors"
Trax builds a junction through its single public constructor. A junction with more than one, or none, cannot be built, so the startup chain check refuses the host naming the junction and the count. Give it exactly one public constructor.
"Junction 'X' (train 'Y') needs 'Z' as its input, but nothing earlier in the chain produced one"
The chain could not find a value of the junction's input type in Memory, and the container does
not register one either. The startup check reports the same problem as
step N (X) needs 'Z' in Memory and nothing before it puts one there.
Causes:
- A previous junction didn't return or add the expected type to Memory
- Type mismatch between junction output and next junction's input
Fix: Check the chain flow. Each junction's input type must exist in Memory (seeded automatically from the train input, or from a previous junction's output):
// Memory starts with: { CreateUserRequest, Unit }
Chain<ValidateJunction>() // Takes CreateUserRequest, returns Unit
.Chain<CreateUserJunction>() // Takes CreateUserRequest, returns User
.Chain<SendEmailJunction>(); // Takes User (from previous junction)The startup chain verification catches these before the host serves traffic: it refuses to start and names the junction and the missing type. (The compile-time Analyzer is deprecated and no longer reports CHAIN001.)
"Unable to resolve service for type 'X' while attempting to activate 'Y'"
This message comes from the .NET DI container, not from Trax: something it is building, usually a
train or a service the train depends on, asks for a type nothing registers. The startup check
reports a train's unregistered dependency itself, as "X cannot be built" above, so this raw message
reaches you only for a train the check did not verify: one it skipped with a "was not verified at
startup" warning, or a host where the check does not run (the Lambda runner, a bare
ServiceProvider, SkipChainVerification()).
Fix: register the missing type before building the host:
services.AddScoped<IUserRepository, UserRepository>();"Manifest group 'X' is given two different MaxActiveJobs values"
Two schedules that share a group each state the same group setting (MaxActiveJobs, Priority or Enabled) with different values. Every start writes a stated group setting, so the value in force would depend on which manifest was seeded last, and AddScheduler refuses it. The message names both schedules.
Fix: state the setting on one member of the group, or the same value on each. A member that says only .Group("name") leaves the group's settings alone.
"Batch 'X' prunes manifests whose external ID starts with ..."
One batch's prune prefix starts another batch's, so the first would delete the second's manifests at every start. A name-based ScheduleMany(name, ...) prunes only within its own group, so this is raised only when the groups do not keep the two apart: an explicit PrunePrefix, or two name-based batches moved into one group.
Fix: rename one batch so neither name plus - starts the other, or keep the batches in separate groups.
The same message, ending "which includes '...', scheduled on its own", means a single Schedule falls inside a batch's prune: its external ID starts with the batch's prefix and, for a name-based batch, it is in the batch's group (Schedule("sync-extra", ..., o => o.Group("sync")) beside ScheduleMany("sync", ...), or a plain Schedule("sync-extra") beside a batch with PrunePrefix("sync-")). Each start the batch would delete it with its history and its own schedule would create it again.
Fix: give the single schedule an external ID outside the prefix, put it in a different group (name-based batches only), or make it one of the batch's items.
"Invalid cron expression: 'X'. Expected 5 or 6 space-separated fields."
Schedule.FromCron, which every Cron helper goes through, parses the expression when the
schedule is created and throws this FormatException for an expression with the wrong number of
fields, such as a seven-field one. An expression with the right number of fields but a value out of
range, such as Cron.Daily(hour: 25) or Cron.Hourly(minute: 60), is refused by the Cronos parser
with its own CronFormatException, which is also a FormatException. Fix the value at the call the
stack trace names.
"Cron expression 'X' never fires: it has no occurrence in the next 10 years."
The expression parses but names a date that does not exist, such as 0 0 30 2 * (February 30th).
Check the day of the month against the months it names.
"A schedule interval must be at least one second."
Schedule.FromInterval, and so every Every helper, throws this ArgumentOutOfRangeException
for an interval shorter than one second, including Every.Seconds(0). The manifest stores whole
seconds, so a shorter interval would be stored as zero.
"The type or namespace name 'IManifestProperties' could not be found"
The compiler's CS0246. IManifestProperties lives in the Trax.Effect package, not in
Trax.Scheduler, in the namespace Trax.Effect.Models.Manifest.
Fix:
using Trax.Effect.Models.Manifest;Train completes but metadata shows "Failed"
Check FailureException and FailureReason in the metadata record for details. FailureJunction identifies which junction threw, and StackTrace points to the original throw site. Common causes:
- An effect provider failed during
SaveChanges(database connection, serialization error) - A junction threw after the main train logic completed
If you catch the exception outside Trax, you'll see the original exception type and message. Structured junction context is available via exception.Data["TrainExceptionData"].
Junctions execute out of order or skip unexpectedly
If you're using ShortCircuit, remember that throwing an exception means "continue" not "stop." See ShortCircuit for details or SDK Reference: ShortCircuit for all overloads.
Scheduled jobs don't execute (no errors)
Possible causes:
- The manifest's
IsEnabledisfalse. Check viaITraxScheduleror the database. A disabled manifest's already-queued scheduled entries also wait,Queued, until it is re-enabled; only a trigger or a dead-letter requeue runs it while disabled. A restart does not re-enable a manifest or group that was disabled at runtime unless the code states.Enabled(true)(see What a Restart Rewrites) - A new cron schedule has not reached its first occurrence yet. It first runs at its first occurrence after it was scheduled, not on the next poll, and cron times are UTC:
Cron.Daily(hour: 3)is 03:00 UTC. The manifest'sNextScheduledRunshows when that is ManifestManagerPollingIntervalorJobDispatcherPollingIntervalis set too high and the job hasn't been picked up yet- The train's input type doesn't implement
IManifestProperties - Your train assembly isn't registered with
AddMediator(). Make sure to pass the assembly containing your trains
SDK Reference
AddTrax / AddEffects | AddMediator | AddTraxDashboard | AddTraxGraphQL | UseInMemory