cloudflare/cloudflared · error

failed to write app token to disk

Error message

failed to write app token to disk

What it means

getToken returns this error when the org token was successfully exchanged for an app token, but writing the resulting app token to appTokenPath via os.WriteFile fails. This is the final persistence step of the exchange flow; if the write fails, the caller receives no token even though the exchange succeeded.

Source

Thrown at token/token.go:380

		if err != nil {
			return "", errors.Wrap(err, "failed to generate org token file path")
		}

		orgTokenLock, orgLockErr := acquireLockFile(orgTokenPath, log)
		if orgLockErr != nil {
			return "", errors.Wrap(orgLockErr, "failed to acquire org token lock")
		}
		defer orgTokenLock.release()
		// check if an org token has been created since the lock was acquired
		orgToken, orgTokenErr = GetOrgTokenIfExists(appInfo.AuthDomain)
	}
	if orgTokenErr == nil {
		if appToken, exchangeErr := exchangeOrgToken(appURL, orgToken); exchangeErr != nil {
			log.Debug().Msgf("failed to exchange org token for app token: %s", exchangeErr)
		} else {
			// generate app path
			if err := os.WriteFile(appTokenPath, []byte(appToken), 0600); err != nil { // nolint: gosec
				return "", errors.Wrap(err, "failed to write app token to disk")
			}
			return appToken, nil
		}
	}
	return getTokensFromEdge(appURL, appInfo.AppAUD, appTokenPath, orgTokenPath, useHostOnly, autoClose, isFedramp, log)
}

// getTokensFromEdge will attempt to use the transfer service to retrieve an app and org token, save them to disk,
// and return the app token.
func getTokensFromEdge(appURL *url.URL, appAUD, appTokenPath, orgTokenPath string, useHostOnly bool, autoClose bool, isFedramp bool, log *zerolog.Logger) (string, error) {
	// If no org token exists or if it couldn't be exchanged for an app token, then run the transfer service flow.

	// this weird parameter is the resource name (token) and the key/value
	// we want to send to the transfer service. the key is token and the value
	// is blank (basically just the id generated in the transfer service)
	resourceData, err := RunTransfer(appURL, appAUD, keyName, keyName, "", true, useHostOnly, autoClose, isFedramp, log, appTokenPath+".url")
	if err != nil {
		return "", errors.Wrap(err, "failed to run transfer service")

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Verify ~/.cloudflared exists and is writable right now: ls -ld ~/.cloudflared; recreate it if removed
  2. Check disk space (df -h) and inode/quota limits
  3. Look for security software or cleanup jobs interfering with token file writes
  4. Retry the token fetch once the directory is stable; the exchange itself succeeded so the retry should be quick

Example fix

// before: write fails silently into a deleted directory
if err := os.WriteFile(appTokenPath, []byte(appToken), 0600); err != nil {
	return "", errors.Wrap(err, "failed to write app token to disk")
}
// after: ensure the directory exists immediately before the write
if err := os.MkdirAll(filepath.Dir(appTokenPath), 0700); err != nil {
	return "", errors.Wrap(err, "failed to ensure token directory")
}
if err := os.WriteFile(appTokenPath, []byte(appToken), 0600); err != nil {
	return "", errors.Wrap(err, "failed to write app token to disk")
}
Defensive patterns

Strategy: try-catch

Validate before calling

// check the directory exists and is writable right before the write
if err := syscall.Access(filepath.Dir(appTokenPath), syscall.W_OK); err != nil {
	os.MkdirAll(filepath.Dir(appTokenPath), 0700)
}

Try / catch

token, err := FetchToken(...)
if err != nil && strings.Contains(err.Error(), "failed to write app token to disk") {
	// exchange succeeded; a one-shot retry usually recovers
	err = ensureTokenDir(); if err == nil { token, err = FetchToken(...) }
}

Prevention

When it happens

Trigger: FetchToken -> getToken -> exchangeOrgToken succeeds, then os.WriteFile(appTokenPath, ...) fails: directory removed between lock acquisition and write, permission change, disk full, or the path becoming invalid (e.g. too long after hostname changes).

Common situations: ~/.cloudflared deleted or chmod'd while cloudflared was running (common with tmp-cleanup jobs); quota exhaustion; AV/EDR software blocking writes of token-like files; concurrent cleanup removing the directory the lock was held in.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06). Data as JSON: /api/errors/b8d9a16bba623293. Report an issue: GitHub.