Skip to main content

DispatchError

Enum DispatchError 

Source
pub enum DispatchError {
    Command(CommandError),
    NoSuchRepo(RepoRef),
    Denied {
        principal: String,
        repo: RepoRef,
        needed: Access,
        held: String,
    },
    Standby {
        principal: String,
        repo: RepoRef,
        browse: bool,
    },
    Twin {
        principal: String,
        repo: RepoRef,
    },
    AnonymousElsewhere {
        repo: RepoRef,
        browse_host: Option<String>,
    },
    Plan {
        namespace: String,
        line: String,
    },
    Store(Error),
}
Expand description

Why an exec request was not served.

Variants§

§

Command(CommandError)

The command did not parse. The bash case, and every other one.

§

NoSuchRepo(RepoRef)

The repository does not exist.

§

Denied

The principal does not hold enough access.

Fields

§principal: String

Who asked.

§repo: RepoRef

What they asked for.

§needed: Access

What the command requires.

§held: String

What they actually hold, rendered as none when they hold nothing.

§

Standby

This box is a read-only STANDBY and the write is refused.

A distinct arm, not a DispatchError::Denied: the writer holds the grant — on a primary this push would land — so “you do not have access” would be a lie that sends them to an administrator about permissions. The honest sentence is that this box does not take client writes; push to the primary. It is only ever reached by a principal who already passed the grant check, so it leaks no repository the writer could not already see.

Fields

§principal: String

Who asked.

§repo: RepoRef

The repository they tried to write.

§browse: bool

The box is a browse replica, so the refusal says so: a stranger who found a public repository here and tried to push to it should learn what this box is, not merely that it is frozen.

§

Twin

This box is a TWIN and serves no git at all — not a read, not a write, not to anybody (2026-09-21, lane T4).

Its own arm and not a DispatchError::Standby, because a standby is a box that serves reads and refuses writes, and a twin is a box that serves NEITHER: serve.rs says why in one line — a read would grow the caches the twin must not hold — and the twin’s whole value is that its disk holds the primary’s log and nothing derived.

Why this exists at the door rather than only at the bind. The bind is chosen ONCE, in serve::start, from stores.is_twin() at that instant. A box becomes a twin over the wire, through Bootstrap.BecomeTwin, long after that — and the owner’s one-medium ruling makes that the ONLY way it happens, because a twin is now installed from the same image as a main with no per-box parameter to say which it is. MEASURED under KVM by lane T3, 2026-09-21: a box conferred at runtime kept 0.0.0.0:2222 bound for the rest of its life, the this box is a TWIN warning never printed, and its volume ended up holding five objects.exploded cache files. A socket that cannot be moved without a restart is not a reason to serve git from it in the meantime.

Fields

§principal: String

Who asked.

§repo: RepoRef

What they asked for.

§

AnonymousElsewhere

Anonymous reads are not served here (--anonymous refuse); the browse replica serves them, and browse_host names it when the operator did. Raised before the repository is even looked up, so a stranger learns nothing about what exists on the primary.

Fields

§repo: RepoRef

The repository the stranger asked for.

§browse_host: Option<String>

Where anonymous reads are served, when the operator named it.

§

Plan

The gate chain refuses this: the disk is under its floor (every build), or (metered) a Suspended or Retention tenant’s push or LFS upload, an anonymous read of a lapsed tenant’s public repository, or a push at the pack quota.

Its own arm for the same reason DispatchError::Standby is one: the caller holds the grant, so “you do not have access” would be a lie that sends a paying customer to an administrator about permissions. The honest sentence names the fact — free bytes and the floor, or the state, the date and where to renew — and it is only ever reached AFTER the grant check admitted the request, so it leaks nothing about a repository the caller could not already see.

Fields

§namespace: String

The tenant.

§line: String

What the client is told, verbatim.

§

Store(Error)

The identity table or the catalog could not be read.

Deliberately not folded into DispatchError::Denied. The tables live in storage (the Arrow-IPC ref log) and a read can fail; answering a storage fault with “you do not have access” would turn an outage into a silent, convincing permission denial that nobody would think to investigate. The ACL model is explicit that may(...) returns Result<bool> for this reason, and this variant is where that Result lands.

Implementations§

Source§

impl DispatchError

Source

pub fn client_message(&self) -> Option<String>

What the client is told.

None means “answer with a channel failure and say nothing”: the request was not even a git command, so there is no git client on the other end that would understand a pkt-line.

“No such repository” and “no permission” produce the same string on purpose. A client that could tell them apart could enumerate private repositories one guess at a time. The server log keeps the distinction; the wire does not.

Those two arms are one arm, and were two identical format!s until 2026-08-05. That is the twinning one implementation forbids, and this is the case it forbids it for: the property is that the two strings are equal, so two copies of the string put the property one careless edit away from being false, in the direction where the wire starts leaking which private repositories exist. a_missing_repository_and_a_forbidden_one_are_one_string_to_the_client guards it from the far side of a real session; this makes the guard hard to need.

Trait Implementations§

Source§

impl Debug for DispatchError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for DispatchError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for DispatchError

Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0:

use the Display impl or to_string()

1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0:

replaced by Error::source, which can support downcasting

Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<CommandError> for DispatchError

Source§

fn from(source: CommandError) -> Self

Converts to this type from the input type.
Source§

impl From<Error> for DispatchError

Source§

fn from(source: Error) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<'a, T, E> AsTaggedExplicit<'a, E> for T
where T: 'a,

Source§

fn explicit(self, class: Class, tag: u32) -> TaggedParser<'a, Explicit, Self, E>

Source§

impl<'a, T, E> AsTaggedImplicit<'a, E> for T
where T: 'a,

Source§

fn implicit( self, class: Class, constructed: bool, tag: u32, ) -> TaggedParser<'a, Implicit, Self, E>

Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> ErrorExt for T
where T: Error + Send + Sync + 'static,

Source§

fn raise(self) -> Exn<Self>
where Self: Sized,

Raise this error as a new exception.
Source§

fn and_raise<T>(self, context: T) -> Exn<T>
where T: Error + Send + Sync + 'static, Self: Sized,

Raise this error as a child of a new exception with the given context error. Read more
Source§

fn raise_erased(self) -> Exn
where Self: Sized,

Raise this error as a new exception, with type erasure.
Source§

fn raise_all<T, I>(self, sources: I) -> Exn<Self>
where Self: Sized, T: Error + Send + Sync + 'static, I: IntoIterator, <I as IntoIterator>::Item: Into<Exn<T>>,

Raise this error as a new exception, with sources as causes.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more