Skip to content

What rtb-app does not do

A single page for every question whose honest answer is "no, that is not supported". Grouped by what you might have been trying to do.

It does not run a CLI

  • No argument parsing. There is no clap in this crate and no dependency on one. Command::run receives an App that someone else built from arguments someone else parsed. That someone is rtb-cli.
  • No --help and no banner. CommandSpec::about and long_about are strings this crate stores; rendering them is rtb-cli's job.
  • No dispatch. BUILTIN_COMMANDS is a slice. Nothing in rtb-app reads it, filters it by the runtime feature set, deduplicates it or calls anything in it.
  • No process exit codes. Command::run returns miette::Result<()>; turning that into an exit status happens above.

It does not do I/O

  • It does not read config files. App stores whatever Config<C> it was handed. Layering, file discovery and merging are rtb-config's.
  • It does not read or write assets. App.assets is an Arc<Assets> from rtb-assets, stored and handed back.
  • It does not resolve credentials. It enumerates what a CredentialProvider reports. Reading a keychain, an environment variable or a file is rtb-credentials'.
  • It does not download, verify or install an update. Every update_* field on ToolMetadata is a declaration rtb-update acts on.
  • It spawns no tasks. Nothing in this crate calls tokio::spawn. The only async in it is the Command::run signature.

Things you cannot configure

  • App's shutdown token cannot be supplied. App::new always creates a fresh root CancellationToken. There is no parameter and no builder method, so an App cannot be made a child of an outer cancellation scope at construction. Derive children from app.shutdown after the fact.
  • The regex limits are fixed. MAX_PATTERN_LEN (1 KiB), SIZE_LIMIT (1 MiB) and DFA_SIZE_LIMIT (8 MiB) are pub const with no override. A tool needing different bounds builds its own RegexBuilder and owns the risk.
  • There are no Cargo features. Neither crate declares any, so nothing here is conditionally compiled and default-features = false changes nothing.
  • Commands cannot be created at runtime. Every CommandSpec field is 'static. A subcommand whose name comes from a config file is not expressible.
  • Pre-run hooks cannot capture state. PreRunHook is a fn pointer, not a closure. Everything a hook needs must come off the App it is handed.

Combinations that do not work

  • A typed config through App::new gives you no schema. config_schema() and config_value() return None unless a TypedConfigOps bundle was attached via with_typed_config. App::new cannot build one — its bound on C is only DeserializeOwned, while the bundle needs Serialize and JsonSchema too. So typed_config::<C>() returning Some while config_schema() returns None is a reachable and legitimate state.
  • update_policy alone does nothing. It is inert unless Feature::Update is enabled and release_source is set. Setting it to Enabled on a tool with no release source changes nothing.
  • Signing keys cannot come from a config file. update_public_keys, update_checksums_asset, update_asset_pattern and telemetry_notice all carry #[serde(skip)]. And because ToolMetadata is deny_unknown_fields, putting one in a YAML document does not silently do nothing — it fails the whole parse.
  • release_credential does not round-trip. It deserialises and is never serialised back; it wraps a secret, and secrets do not leave through Serialize.
  • ReleaseSource::Gitea has no host default. Every other hosted variant defaults its host. Gitea cannot, because there is no public instance to default to. Use Codeberg for codeberg.org.

Silent failure modes

The things that go wrong without an error message. Each is covered in more detail on its own page; this is the checklist.

  • FeaturesBuilder::default() is empty, FeaturesBuilder::new() is the nine defaults. Reaching for Default::default() out of habit ships a CLI with every built-in command hidden, and nothing warns.
  • A missing #[distributed_slice] attribute compiles fine. The command simply never appears. Assert its name is in BUILTIN_COMMANDS in a test.
  • VersionInfo::from_pkg_version never fails. An unparseable string becomes 0.0.0 with no error and no log line. The only signal is that is_development() then returns true.
  • VersionInfo::from_env() reports the wrong version. Deprecated since 0.9.0. It returns rtb-app's version rather than the calling tool's, which breaks self-update's post-download verification.
  • TypedConfigOps::render returns None on a type mismatch, with no error describing it — so a mismatched ops/config pair produces a permanently empty config show.
  • Schema serialisation failure becomes null. TypedConfigOps::new uses unwrap_or(Value::Null), so a JsonSchema implementation that fails to serialise yields a null schema rather than a message.
  • An empty credentials listing is indistinguishable from no provider. Both return an empty Vec.
  • A Compile regex error covers two different problems. Invalid syntax and exceeding the memory bounds arrive as the same variant, under a message that reads as a size problem.

Ordering guarantees that do not exist

  • Link-time registration order is not deterministic — for commands or for pre-run hooks. Nothing that depends on which entry comes first is safe. Overriding a built-in by re-registering its name relies on rtb-cli's deduplication resolving the collision, not on any ordering this crate promises.
  • Features::iter() has no defined order. It is backed by a HashSet. Sort before printing or comparing.
  • Features does not implement PartialEq. Two sets cannot be compared with ==.
  • App does not implement Debug. Printing one, or deriving Debug on a struct holding one, will not compile.