GoogleChrome/lighthouse · warning · Error

Save already in progress

Error message

Save already in progress

What it means

Thrown by GithubApi.createGist to guard against overlapping saves: a class-level _saving flag is set true at the start of a gist creation and only reset in a finally block. A second createGist call while the first is still in flight is rejected outright. This is a re-entrancy guard, not a network error.

Source

Thrown at viewer/app/src/github-api.js:41

    this._saving = false;
  }

  static get LH_JSON_EXT() {
    return '.lighthouse.report.json';
  }

  getFirebaseAuth() {
    return this._auth;
  }

  /**
   * Creates a gist under the users account.
   * @param {LH.Result|LH.FlowResult} jsonFile The gist file body.
   * @return {Promise<string>} id of the created gist.
   */
  async createGist(jsonFile) {
    if (this._saving) {
      throw new Error('Save already in progress');
    }

    logger.log('Saving report to GitHub...', false);
    this._saving = true;

    try {
      const accessToken = await this._auth.getAccessToken();
      let filename;
      if ('steps' in jsonFile) {
        filename = getFlowResultFilenamePrefix(jsonFile);
      } else {
        filename = getLhrFilenamePrefix({
          finalDisplayedUrl: Util.getFinalDisplayedUrl(jsonFile),
          fetchTime: jsonFile.fetchTime,
        });
      }
      const body = {
        description: 'Lighthouse json report',

View on GitHub (pinned to 9515cd4e58)

Solutions

  1. Disable the save UI control while createGist is pending and re-enable it on settlement (success or error).
  2. Await/queue the existing save instead of starting a new one — guard with a local promise or debounce.
  3. Only call createGist when not already saving (check the in-flight promise at the call site).

Example fix

// before
saveBtn.onclick = () => githubApi.createGist(json); // double-click throws

// after
let pending = null;
saveBtn.onclick = async () => {
  if (pending) return pending;            // reuse in-flight save
  saveBtn.disabled = true;
  pending = githubApi.createGist(json).finally(() => {
    pending = null;
    saveBtn.disabled = false;
  });
  return pending;
};
Defensive patterns

Strategy: validation

Validate before calling

let pendingSave = null;
async function safeCreateGist(json) {
  if (pendingSave) return pendingSave;
  pendingSave = githubApi.createGist(json).finally(() => { pendingSave = null; });
  return pendingSave;
}

Type guard

// The _saving flag is internal; mirror it at the call site with an in-flight promise.
const isAlreadySaving = () => pendingSave !== null;

Try / catch

try {
  await safeCreateGist(json);
} catch (err) {
  if (err.message === 'Save already in progress') {
    // ignore — a save is already running; optionally await it
  } else throw err;
}

Prevention

When it happens

Trigger: Invoking createGist a second time before the first awaited call resolves — e.g. double-clicking the 'Save as gist' button, or two callers racing to save.

Common situations: Users double-clicking the save button; a UI that does not disable the control during the async save; programmatic retry triggered before the prior promise settles.

Related errors


AI-assisted analysis of GoogleChrome/lighthouse@9515cd4e58 (2026-08-13). Data as JSON: /api/errors/ad9be17f8fbc2f79. Report an issue: GitHub.