mastra-ai/mastra · error

Failed to remove GitHub token (${res.status})

Error message

Failed to remove GitHub token (${res.status})

What it means

deleteGithubPat sends DELETE to `${baseUrl}/web/github/pat?kind=...` and throws this error on any non-ok response. It means the server refused or failed to remove the stored GitHub PAT for that kind.

Source

Thrown at mastracode/factory-ui/src/ui/domains/workspaces/services/github.ts:183

    method: 'POST',
    headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
    credentials: 'include',
    body: JSON.stringify({ token, kind }),
  });
  if (!res.ok) {
    const body = (await res.json().catch(() => undefined)) as { error?: string } | undefined;
    throw new Error(body?.error ?? `Failed to save GitHub token (${res.status})`);
  }
}

/** Remove an org GitHub PAT. */
export async function deleteGithubPat(baseUrl: string, kind: GithubPatKind = 'default'): Promise<void> {
  const res = await fetch(`${baseUrl}/web/github/pat?kind=${kind}`, {
    method: 'DELETE',
    headers: { Accept: 'application/json' },
    credentials: 'include',
  });
  if (!res.ok) throw new Error(`Failed to remove GitHub token (${res.status})`);
}

/** List repos across the user's installations, optionally filtered by query. */
export async function listGithubRepos(baseUrl: string, query?: string): Promise<GithubRepo[]> {
  const url = query ? `${baseUrl}/web/github/repos?q=${encodeURIComponent(query)}` : `${baseUrl}/web/github/repos`;
  const res = await fetch(url, { headers: { Accept: 'application/json' }, credentials: 'include' });
  if (!res.ok) throw new Error(`Failed to list repos (${res.status})`);
  const body = (await res.json()) as { repos: GithubRepo[] };
  return body.repos;
}

/** The GitHub source-control integration id registered on the server. */
const GITHUB_INTEGRATION_ID = 'github';

/** A Factory project row from `/web/factory/projects`. */
export interface FactoryProjectPayload {
  id: string;
  name: string;

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Check the status: 401/403 → re-authenticate or confirm the user owns the PAT; 404 → treat as already deleted and refresh local state
  2. Retry once after session refresh; transient 5xx on revocation is common
  3. Ensure the `kind` argument matches one registered on the server (default is 'default')
  4. Verify baseUrl/origin so the session cookie is included via credentials: 'include'

Example fix

// before
await deleteGithubPat(baseUrl); // assume 404 is fine
// after
try {
  await deleteGithubPat(baseUrl, kind);
} catch (e) {
  if (!/\(404\)/.test(e.message)) throw e; // tolerate already-removed
  queryClient.invalidateQueries(githubPatKeys.all);
}
Defensive patterns

Strategy: try-catch

Validate before calling

function canAttemptPatDelete(kind: unknown): boolean {
  return typeof kind === 'string' && kind.length > 0;
}

Type guard

const isAlreadyRemoved = (e: unknown): boolean => e instanceof Error && /\(404\)/.test(e.message);

Try / catch

try {
  await deleteGithubPat(baseUrl, kind);
} catch (e) {
  if (isAlreadyRemoved(e)) {
    queryClient.invalidateQueries(githubPatKeys.all); // idempotent success
  } else if (/\((401|403)\)/.test(e.message)) {
    promptReauth();
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Non-ok response from DELETE /web/github/pat: 401 unauthenticated session, 403 forbidden (PAT belongs to another user/org scope), 404 unknown kind or no stored PAT, 5xx server error during removal or GitHub revocation.

Common situations: Deleting a PAT after the session expired; calling delete twice and the second call hits 404 (no idempotent handling here, unlike unlinkRepository); wrong `kind` value passed (e.g. 'default' vs a custom kind the server doesn't recognize).

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/1777a6464c66e823. Report an issue: GitHub.