Skip to content

Fix: treat "version is the current active version" as a successful deploy (#461)#462

Open
tuesd4y wants to merge 2 commits into
FirebaseExtended:mainfrom
tuesd4y:fix/already-active-version-deploy
Open

Fix: treat "version is the current active version" as a successful deploy (#461)#462
tuesd4y wants to merge 2 commits into
FirebaseExtended:mainfrom
tuesd4y:fix/already-active-version-deploy

Conversation

@tuesd4y

@tuesd4y tuesd4y commented Jun 30, 2026

Copy link
Copy Markdown

Fixes #461. A Firebase Hosting deploy can fully succeed with created version, uploaded files, version finalized, site serving the new content but the action might still exit with an error when the version being released is already the live/active version:

FAILED_PRECONDITION
Can't release to projects/-/sites//channels/live:
supplied version projects/.../versions/xxxx is the current active version

Note that this is not only for the live channel, but can happen for all channels.

This is effectively a successful no-op, but the CLI returns a non-zero exit code, so the workflow fails. This PR detects that specific error and treats it as a successful deploy.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds handling for Firebase's 'already active version' error, treating it as a successful no-op deployment rather than a failure. For preview deploys, it retrieves the existing channel details, while for production deploys, it returns a success status directly. The review feedback highlights two important improvements: first, ensuring the 'already active version' check is not bypassed when debugging is enabled, and second, wrapping the channel list retrieval in a try-catch block to prevent transient errors from failing an otherwise successful deployment.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/deploy.ts
Comment on lines 134 to 154
if (!debug) {
console.log(
"Retrying deploy with the --debug flag for better error output"
);
await execWithCredentials(args, projectId, gacFilename, {
debug: true,
firebaseToolsVersion,
force,
});
if (
isAlreadyActiveVersionError(output) ||
isAlreadyActiveVersionError(e.message)
) {
console.log(
"The deployed version is already the current active version; treating as a successful no-op deploy."
);
} else {
console.log(
"Retrying deploy with the --debug flag for better error output"
);
await execWithCredentials(args, projectId, gacFilename, {
debug: true,
firebaseToolsVersion,
force,
});
}
} else {
throw e;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If debug is true (for example, if the action is run with debug options or during a retry), the catch block will immediately execute the else branch and throw the error, completely bypassing the isAlreadyActiveVersionError check. This means an "already active version" error will still cause the deployment to fail when debugging is active.

We should check isAlreadyActiveVersionError first, before checking if (!debug).

    if (
      isAlreadyActiveVersionError(output) ||
      isAlreadyActiveVersionError(e.message)
    ) {
      console.log(
        "The deployed version is already the current active version; treating as a successful no-op deploy."
      );
    } else if (!debug) {
      console.log(
        "Retrying deploy with the --debug flag for better error output"
      );
      await execWithCredentials(args, projectId, gacFilename, {
        debug: true,
        firebaseToolsVersion,
        force,
      });
    } else {
      throw e;
    }

Comment thread src/deploy.ts
Comment on lines +195 to +237
async function getExistingChannel(
gacFilename: string,
deployConfig: ChannelDeployConfig
): Promise<ChannelSuccessResult> {
const { projectId, channelId, target, firebaseToolsVersion } = deployConfig;

const listText = await execWithCredentials(
["hosting:channel:list", ...(target ? ["--site", target] : [])],
projectId,
gacFilename,
{ firebaseToolsVersion }
);

const list = JSON.parse(listText.trim()) as ChannelListResult | ErrorResult;

const channel =
list.status === "success"
? list.result.channels.find((c) =>
c.name.endsWith(`/channels/${channelId}`)
)
: undefined;

if (!channel) {
console.log(
`Could not find channel "${channelId}" when reading back the already-active deploy; reporting success without URL details.`
);
}

const site =
channel?.name.match(/\/sites\/([^/]+)\//)?.[1] ?? target ?? channelId;

return {
status: "success",
result: {
[site]: {
site,
...(target ? { target } : {}),
url: channel?.url ?? "",
expireTime: channel?.expireTime ?? "",
},
},
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If hosting:channel:list fails (due to transient network issues, permission errors, etc.) or returns invalid JSON, getExistingChannel will throw an unhandled exception. This will cause the entire deployment action to fail, even though the actual deployment succeeded (since the "already active version" error indicates the content is already live).

To make this more robust, we should wrap the channel list retrieval in a try-catch block and fall back to returning a successful result with empty URL details if it fails.

async function getExistingChannel(
  gacFilename: string,
  deployConfig: ChannelDeployConfig
): Promise<ChannelSuccessResult> {
  const { projectId, channelId, target, firebaseToolsVersion } = deployConfig;
  const site = target ?? channelId;

  try {
    const listText = await execWithCredentials(
      ["hosting:channel:list", ...(target ? ["--site", target] : [])],
      projectId,
      gacFilename,
      { firebaseToolsVersion }
    );

    const list = JSON.parse(listText.trim()) as ChannelListResult | ErrorResult;

    const channel =
      list.status === "success"
        ? list.result.channels.find((c) =>
            c.name.endsWith(`/channels/${channelId}`)
          )
        : undefined;

    if (!channel) {
      console.log(
        `Could not find channel "${channelId}" when reading back the already-active deploy; reporting success without URL details.`
      );
      if (list.status === "error") {
        console.log(`Channel list error: ${list.error}`);
      }
    }

    const resolvedSite =
      channel?.name.match(/\/sites\/([^/]+)\/\//)?.[1] ?? site;

    return {
      status: "success",
      result: {
        [resolvedSite]: {
          site: resolvedSite,
          ...(target ? { target } : {}),
          url: channel?.url ?? "",
          expireTime: channel?.expireTime ?? "",
        },
      },
    };
  } catch (e) {
    console.log(
      `Failed to retrieve channel list: ${e.message}. Reporting success without URL details.`
    );
    return {
      status: "success",
      result: {
        [site]: {
          site,
          ...(target ? { target } : {}),
          url: "",
          expireTime: "",
        },
      },
    };
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Deployment succeeds but action fails with: "supplied version is the current active version"

1 participant