microsoft/aspire · error · InvalidOperationException
Could not read a valid start time for monitor process
Error message
Could not read a valid start time for monitor process {processId} from '{procRoot}'. What it means
On Linux, the DCP watchdog reads the process start time from /proc/<pid>/stat (boot-relative start ticks, field 22) via a shared ProcessStartTimeHelper. If the value cannot be read or parsed (process gone, /proc unavailable, malformed stat), this InvalidOperationException is thrown instead of using a bogus timestamp that could misidentify a recycled PID.
Solutions
- Ensure /proc is mounted and readable for the target PID in the container/environment
- Check security policies (seccomp, AppArmor, SELinux) that block reading /proc/<pid>/stat and relax them for the AppHost process
- Re-run the AppHost if it was a transient race with process exit
Defensive patterns
Strategy: fallback
Validate before calling
static bool CanReadProcStat(int pid, string procRoot = "/proc")
=> File.Exists($"{procRoot}/{pid}/stat"); Type guard
bool ProcStatReadable(int pid) => OperatingSystem.IsLinux() && File.Exists($"/proc/{pid}/stat"); Try / catch
try
{
var ts = DcpProcessMonitor.GetMonitorProcessIdentity(parentProcess);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("/proc"))
{
// Disable watchdog or remount /proc readable, then retry.
} Prevention
- Ensure /proc is mounted in the container running the AppHost
- Check seccomp/AppArmor/SELinux policies allow reading /proc/<pid>/stat
- Avoid minimal images that strip /proc; use standard base images
When it happens
Trigger: Running the AppHost on Linux when ProcessStartTimeHelper.TryGetLinuxProcessStartTicks fails: the monitor process already exited, /proc is not mounted or is restricted (hardened containers, seccomp), or the procRoot path is wrong.
Common situations: Running Aspire inside minimal/stripped containers without /proc; AppArmor/SELinux policies blocking /proc/<pid>/stat reads; PID namespace mismatches where the target PID is not visible.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- Could not determine the identity timestamp for monitor…
- At least one process command success exit code must be…
- 'cargo metadata' for the Rust app
- DCP fork-process did not return a child process ID. DCP…
- DCP fork-process did not return a valid child process ID…
AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16).
Data as JSON: /api/errors/d72270519149a5d4.
Report an issue: GitHub.
Appendix: source
Thrown at src/Aspire.Hosting/Dcp/DcpProcessMonitor.cs:61
{
return null;
}
}
private static DateTime GetLinuxProcessIdentityTimestamp(int processId)
{
// DCP inspects the *host* process table, so honor HOST_PROC when this check runs inside a
// container whose host /proc is mounted elsewhere. Orphan/liveness detection deliberately uses the
// current namespace's /proc instead (see ProcessStartTimeHelper.TryGetProcessStartTime), which is
// why the /proc root is a parameter of the shared reader rather than baked into it.
var procRoot = Environment.GetEnvironmentVariable("HOST_PROC") ?? "/proc";
// Share the /proc start-ticks reader with every other Aspire watchdog so there is one Linux
// process-identity implementation. The value is boot-relative (field 22 of /proc/<pid>/stat),
// which is why it is immune to wall-clock drift.
if (ProcessStartTimeHelper.TryGetLinuxProcessStartTicks(processId, procRoot) is not { } startTicks)
{
throw new InvalidOperationException($"Could not read a valid start time for monitor process {processId} from '{procRoot}'.");
}
// Convert the boot-relative start ticks into a DateTime offset from DateTime.MinValue instead of
// estimating a wall-clock time. Kept in milliseconds for parity with the timestamp DCP compares
// against, which is a distinct identity domain from the whole-second value used by the orphan
// detectors, so the two never cross-compare.
var startTimeMilliseconds = (startTicks * 1000) / (ulong)ProcessStartTimeHelper.GetLinuxClockTicksPerSecond();
return DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc).AddMilliseconds(startTimeMilliseconds);
}
}
View on GitHub (pinned to 25830f84bd)