zed-industries/zed · error · anyhow::Error

missing auto-resolution outcomes

Error message

missing auto-resolution outcomes

What it means

Internal invariant error in the agent thread's permission flow: settings-based auto-resolution is active (`check_settings` is Some) but `auto_resolution_outcomes` — the canned Allow/Deny outcomes used to auto-resolve the prompt — is None. The two arguments must be supplied together; this error means a caller violated that contract. It is a programming error, not a runtime condition.

Source

Thrown at crates/agent/src/thread.rs:6454

                        context,
                        kind: acp_thread::AuthorizationKind::PermissionGrant,
                    },
                )))
            {
                log::error!("Failed to send tool call authorization: {error}");
                return Err(anyhow!("Failed to send tool call authorization: {error}"));
            }

            let Some(check_settings) = check_settings else {
                let outcome = response_rx
                    .await
                    .map_err(|_| anyhow!("authorization channel closed"))?;
                ensure_tool_call_authorization_not_interrupted(&outcome)?;

                return Self::persist_permission_outcome(&outcome, fs, cx);
            };
            let Some((auto_allow_outcome, auto_deny_outcome)) = auto_resolution_outcomes else {
                return Err(anyhow!("missing auto-resolution outcomes"));
            };

            let (mut settings_tx, mut settings_rx) = watch::channel(());
            let _settings_subscription = cx.update(|cx| {
                cx.observe_global::<SettingsStore>(move |_cx| {
                    settings_tx.send(()).ok();
                })
            });

            // Race the user's response against settings changes. On each
            // settings change, re-evaluate `check_settings`: if it now
            // yields a definitive Allow or Deny, resolve the prompt
            // without user interaction. Otherwise keep waiting on the
            // same prompt.
            loop {
                let settings_changed = async {
                    if settings_rx.changed().await.is_err() {
                        std::future::pending::<()>().await;

View on GitHub (pinned to bc538def45)

Solutions

  1. Fix the call site: pass `auto_resolution_outcomes` whenever `check_settings` is Some.
  2. Make the invariant unrepresentable: bundle the callback and its outcomes in a single struct passed as one Option.
  3. Add a debug_assert at function entry so mismatches fail loudly in tests.

Example fix

// before
fn authorize(
    check_settings: Option<CheckFn>,
    auto_resolution_outcomes: Option<(Outcome, Outcome)>, // can diverge from check_settings
)
// after
struct AutoResolution<CheckFn> {
    check: CheckFn,
    allow_outcome: Outcome,
    deny_outcome: Outcome,
}
fn authorize(auto_resolution: Option<AutoResolution<CheckFn>>) // paired or absent, never half
Defensive patterns

Strategy: validation

Validate before calling

// Enforce the pairing at the call boundary:
let (check_settings, auto_resolution_outcomes) = match auto_resolution {
    Some(ar) => (Some(ar.check), Some((ar.allow_outcome, ar.deny_outcome))),
    None => (None, None),
};
debug_assert_eq!(
    check_settings.is_some(),
    auto_resolution_outcomes.is_some(),
    "check_settings requires auto_resolution_outcomes"
);

Prevention

When it happens

Trigger: Invoking the authorization helper with a `check_settings` callback but `auto_resolution_outcomes: None` — e.g. a new call site enabled settings checking without providing outcomes, or a refactor dropped the tuple while keeping the callback.

Common situations: Refactors of the permission flow; new permission modes added without updating every caller; custom agent-thread setups that hand-roll the arguments.

Related errors


AI-assisted analysis of zed-industries/zed@bc538def45 (2026-08-16). Data as JSON: /api/errors/5083d18d985bb520. Report an issue: GitHub.