pub enum FailureReason {
JitRequestFailed,
JitExpired,
RunnerPackageUnverified,
RunnerVersionRejected,
ProcessStartFailed,
ProcessExitedUnexpectedly,
RegistrationTimedOut,
TerminatedAfterRegistrationTimeout,
Other(String),
}Expand description
Why an attempt failed.
Other exists so e3 is not blocked by a reason this task did not
anticipate; crates/domain/src/attempt.rs belongs to b1 and e3 cannot
extend this enum itself.
Nothing that reaches this type may contain a credential. It is written to
the journal by b2 and rendered by g2, and 07-security.md’s log scan runs
over both. A JIT blob, an Authorization header, or a token in an Other
string would defeat that gate from inside the domain, where the redacting log
sink (d1) never gets a chance to see it.
Variants§
JitRequestFailed
generate-jitconfig did not return a configuration.
JitExpired
The configuration was never claimed and expired (flow 4.4).
RunnerPackageUnverified
The runner package’s published checksum was absent or did not match
(05-infrastructure.md: the agent fails closed).
RunnerVersionRejected
GitHub rejects runners more than 30 days behind the latest release
(01-current-architecture.md, edge case 7). Terminal and
operator-actionable, never retried.
ProcessStartFailed
The child process could not be spawned.
ProcessExitedUnexpectedly
The child process exited before it could do its one job.
Only for a process that is actually gone. A runner still running past
its startup deadline is Self::RegistrationTimedOut, not this: g2
renders these strings to an operator, and telling one that a process
“exited unexpectedly” while it is visible in Task Manager spends the
credibility of every other message this product prints.
RegistrationTimedOut
The runner process is up but never registered with GitHub inside its startup window.
Split from Self::ProcessExitedUnexpectedly because it is accurate and
because it points an operator somewhere else entirely. A process that
exited is a crash to investigate — logs, exit code, a corrupt runner
package. A process that is alive and unregistered has almost always
failed to reach GitHub: a proxy, a firewall, a DNS answer, an expired
or wrong-scoped configuration. Those are configuration and networking
fixes, and an operator sent to the wrong one of the two loses the time
this distinction exists to save.
TerminatedAfterRegistrationTimeout
The agent stopped a runner process that had not registered inside its startup window.
The dead-process counterpart of Self::RegistrationTimedOut, and it
exists because that one cannot be reused here. By the time this reason
is recorded the process is gone — the agent signalled it — so rendering
“the runner process is running but did not register” would tell an
operator a process is up that they can see is not. That is the same false
liveness claim Self::ProcessExitedUnexpectedly’s documentation says
spends the credibility of every other message this product prints, and
tests::no_decision_calls_a_dead_process_live is what holds the line.
It points where Self::RegistrationTimedOut points, not where
Self::ProcessExitedUnexpectedly points. The runner never reached
GitHub — a proxy, a firewall, a DNS answer, an expired or wrong-scoped
configuration — and the exit is the agent’s own doing rather than
evidence of a crash. An operator sent to logs and exit codes for this is
investigating the wrong machine.
Who records it. e3, and only e3. recovery_decision cannot
derive it: a process this agent killed and a process that crashed on its
own present the same RecoveryObservation, so the distinguishing
fact has to be journalled as terminate-intent before the signal is sent
and read back afterwards. See RecoveryDecision::Terminate for the
window that obligation closes.
Other(String)
Anything else. Must carry no credential.
Implementations§
Source§impl FailureReason
impl FailureReason
Sourcepub const ALL: [FailureReason; 9]
pub const ALL: [FailureReason; 9]
One value of every variant, the counterpart of AttemptState::ALL.
Other’s detail is empty because what a caller enumerates is the
variant; no consumer should read the string out of this constant.
This list is hand-written, and what keeps it honest is not its own
length. A length written as 9 next to nine elements asserts
nothing — that was the defect in the assertion this constant replaced.
What catches a new variant is the exhaustive, wildcard-free match in
tests::earliest_state_producing, which stops the test target compiling
the moment one is added and so puts the author in front of this list.
The residual gap, measured rather than assumed. An author who adds a
variant, writes its Display arm and its earliest_state_producing arm,
and then adds it to neither this constant nor the test’s cases table,
gets a green suite with the variant untested. Adding it to exactly one of
the two fails the length check; adding it to neither does not.
Re-measured when TerminatedAfterRegistrationTimeout was added, because
a gap described once and never re-run is a gap nobody knows still exists.
With the variant declared and both match arms written but neither list
touched, cargo test -p runner-manager-domain was green — its lib target
reported 128 passed; 0 failed — with the ninth variant unreachable and
untested. Adding it to this constant alone then failed the length check
with left: 8 / right: 9.
And the compiler never points at this constant. A const array is
unaffected by a new variant, so nothing here errors. What stops the
author is Display::fmt’s match (E0004) and then
tests::earliest_state_producing’s, and neither of those mentions this
list — which is why a note pointing back here sits at each of those two
match sites, where the author is actually standing.
This is closable in stable Rust, and is hand-written anyway. A local
macro_rules! that declares the enum and emits ALL from the same
variant list needs no dependency and no unstable feature
(std::mem::variant_count is unstable, but a declarative macro is not
the same thing). It is not used here because every variant of this enum
carries several paragraphs of its own documentation explaining what an
operator should do about it, and variants declared inside a macro
invocation are markedly worse to read and to rustdoc. That is a
legibility trade, deliberately taken — not an impossibility. If the
documentation ever thins out, the macro is the better answer.
Trait Implementations§
Source§impl Clone for FailureReason
impl Clone for FailureReason
Source§fn clone(&self) -> FailureReason
fn clone(&self) -> FailureReason
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more