gitbutlerapp/gitbutler · error

ForgeNotAuthenticated

ForgeNotAuthenticated

Error message

No GitHub access token found for account '{account_id}'.\nRun 'but config forge auth' to re-authenticate.

What it means

`GitHubClient::from_storage` resolves the account, then looks up its stored GitHub access token; when no token is stored for that account it returns this error, tagged with the `ForgeNotAuthenticated` context (`NOT_AUTHENTICATED`). It means the client cannot authenticate any request — credentials are missing, not invalid.

Solutions

  1. Run `but config forge auth` to authenticate the account with GitHub.
  2. Verify the correct account is selected/resolvable (`resolve_account`) and that it is the one with a stored token.
  3. In CI, provision a token via the setup step before invoking commands that need GitHub.
  4. If the token was removed by the OS keychain, re-authenticate to restore it.

Example fix

// before
let client = GitHubClient::from_storage(Some(&account), storage)?; // panics/errors: no token

// after
// terminal:
// $ but config forge auth
let client = GitHubClient::from_storage(Some(&account), storage)?;
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check that the account has a stored token
let has_token = crate::token::get_gh_access_token(&account_id, storage)?.is_some();
if !has_token { eprintln!("Run 'but config forge auth' first"); }

Try / catch

match GitHubClient::from_storage(Some(&account), storage) {
    Ok(c) => c,
    Err(e) if e.to_string().contains("No GitHub access token found") => {
        return Err(anyhow!("GitHub not authenticated; run 'but config forge auth'"));
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Constructing a `GitHubClient::from_storage` for an account that never completed `but config forge auth`, after tokens were cleared, or when the preferred account identifier names an account with no stored token.

Common situations: Fresh machine or clone without forge setup; running `but` in CI where the auth step was skipped; expired/cleared keychain entries; specifying the wrong account identifier.

Understand the failure class

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/9a7173b20ca2ebc0. Report an issue: GitHub.

Appendix: source

Thrown at crates/but-github/src/client.rs:82

            .default_headers(headers)
            .build()?;

        Ok(Self {
            client,
            base_url: GITHUB_API_BASE_URL.to_string(),
        })
    }

    /// Create a new instance of a GitHub client out of the stored accounts information.
    pub fn from_storage(
        storage: &but_forge_storage::Controller,
        preferred_account: Option<&crate::GithubAccountIdentifier>,
    ) -> anyhow::Result<Self> {
        let account_id = resolve_account(preferred_account, storage)?;
        if let Some(access_token) = crate::token::get_gh_access_token(&account_id, storage)? {
            account_id.client(&access_token)
        } else {
            Err(anyhow::anyhow!(
                "No GitHub access token found for account '{account_id}'.\nRun 'but config forge auth' to re-authenticate."
            )
            .context(NOT_AUTHENTICATED))
        }
    }

    /// Create a new instance of a GitHub client, with a custom base URL.
    ///
    /// This is used to create the GitHub client for Enterprise users.
    pub fn new_with_host_override(access_token: &Sensitive<String>, host: &str) -> Result<Self> {
        let mut headers = HeaderMap::new();
        headers.insert(
            USER_AGENT,
            HeaderValue::from_static("gb-github-integration"),
        );
        headers.insert(
            ACCEPT,
            HeaderValue::from_static("application/vnd.github+json"),

View on GitHub (pinned to 58e5313667)