Skip to content

[Detail Bug] CLI errors hide underlying HTTP status/error cause, making auth failures non-actionable #271

@detail-app

Description

@detail-app

Detail Bug Report

https://app.detail.dev/org_5f375fe3-a706-4e9a-a6f7-800f2439b3f6/bugs/bug_e0844d3e-9426-41a9-97a0-50c10d41af55

Introduced in #129 by @sachiniyer on Mar 6, 2026

Summary

  • Context: The CLI handles all command errors through src/main.rs which formats them before display.
  • Bug: Using {err} format hides the entire error chain, removing essential debugging information (HTTP status codes) that users need to take corrective action.
  • Actual vs. expected: Users see "Failed to fetch repositories" but cannot tell if it's an authentication failure (401), permission issue (403), or not found (404).
  • Impact: Users must guess which diagnostic command to run.

Code with Bug

// src/main.rs:13
let _ = Term::stderr().write_line(&format!("Error: {err}")); // <-- BUG 🔴 Display formatting drops the error chain

Explanation

anyhow error chains typically contain the actionable root cause (e.g., "401 Unauthorized"). Printing errors with Display ({err}) only shows the top-level context message (e.g., "Failed to fetch repositories") and discards the Caused by: chain.

This is observable in the current chain construction:

  • Command adds a generic context (.context("Failed to fetch repositories")).
  • API client wraps an underlying HTTP error (anyhow!("API error: {e}")) that includes status/message.
  • src/main.rs prints only the top-level Display string, so users never see the HTTP status.

As a result, the same 401 condition can yield helpful guidance in detail auth login --token ... but only "Failed to fetch repositories" in detail repos list, forcing users to guess to run detail auth status.

Codebase Inconsistency

Real CLI output shows inconsistent guidance for the same auth failure:

  • detail auth login --token <bad>: "Failed to authenticate. Please check your token."
  • detail repos list with an invalid/expired stored token: "Failed to fetch repositories" (no indication it is auth-related).

Failing Test

// tests/integration.rs
#[test]
fn error_includes_status_code_for_auth_failures() {
    let env = Env::new("error_status_code");
    env.write_config(r#"api_token = "dtl_live_invalid_token""#);

    let out = env.run(&["repos", "list"]);
    assert!(!out.success);
    assert!(
        out.stderr.contains("401") || out.stderr.contains("Unauthorized"),
        "Error should indicate auth failure:\nstderr: {}", out.stderr
    );
}

Recommended Fix

Preserve the intent to avoid dumping verbose headers/structs, but still surface essential HTTP status/message. One approach is in src/api/client.rs: map the progenitor error into a concise message like 401 Unauthorized: <message> (without headers/struct dumps), so the chain contains actionable information even when top-level errors add context.

History

This bug was introduced in commit 92044b7. The commit aimed to fix verbose error output by switching from anyhow's default Debug formatting to Display formatting ({err} instead of {err:?}), but overcorrected by hiding the entire error chain—including essential diagnostic information like HTTP status codes—rather than just the verbose HTTP headers and struct dumps.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions