overleaf/overleaf · error · InvalidError

Cannot switch an archived split test to next phase

Error message

Cannot switch an archived split test to next phase

What it means

Thrown by switchToNextPhase in SplitTestManager.mjs when the split test named in the request exists but has the `archived` flag set. Archived tests are treated as finished/retired and the manager refuses any lifecycle mutation, including phase switches. It surfaces as an InvalidError (HTTP 400) so callers know the request is well-formed but the target state forbids it.

Source

Thrown at services/web/app/src/Features/SplitTests/SplitTestManager.mjs:237

  } catch (error) {
    throw OError.tag(error, 'Failed to merge all split tests, merged set was', {
      merged,
    })
  }
}

async function switchToNextPhase(
  { name, comment, targetPhase, labsUserLimit, clearUserLimit },
  userId
) {
  const splitTest = await getSplitTest({ name })
  if (!splitTest) {
    throw new Errors.NotFoundError(
      `Cannot switch split test with ID '${name}' to next phase: not found`
    )
  }
  if (splitTest.archived) {
    throw new Errors.InvalidError(
      'Cannot switch an archived split test to next phase',
      {
        name,
      }
    )
  }
  const lastVersionCopy = SplitTestUtils.getCurrentVersion(splitTest).toObject()
  lastVersionCopy.versionNumber++

  const currentPhase = lastVersionCopy.phase

  // Determine and validate target phase
  if (targetPhase) {
    const validTransitions = {
      [ALPHA_PHASE]: [LABS_PHASE, BETA_PHASE],
      [LABS_PHASE]: [BETA_PHASE],
      [BETA_PHASE]: [RELEASE_PHASE],
    }

View on GitHub (pinned to 28ad3b03b7)

Solutions

  1. Fetch the test first and check `archived` before calling switchToNextPhase; skip archived tests in batch jobs.
  2. Unarchive the test (clear the `archived` flag) if it genuinely needs to be reactivated, then retry the phase switch.
  3. Filter admin UI / API queries with archived=false so archived tests cannot be selected.

Example fix

// before
await splitTestManager.promises.switchToNextPhase({ name: 'my-test' })
// after
const test = await splitTestManager.promises.getSplitTest({ name: 'my-test' })
if (!test.archived) {
  await splitTestManager.promises.switchToNextPhase({ name: 'my-test' })
}
Defensive patterns

Strategy: validation

Validate before calling

const test = await splitTestManager.promises.getSplitTest({ name })
if (!test || test.archived) throw new Error(`test '${name}' missing or archived`)

Type guard

function canSwitchPhase(test) {
  return test != null && test.archived === false
}

Try / catch

try {
  await switchToNextPhase({ name })
} catch (err) {
  if (err instanceof Errors.InvalidError && /archived/.test(err.message)) {
    // skip or unarchive
  } else throw err
}

Prevention

When it happens

Trigger: Calling switchToNextPhase({ name, ... }) where getSplitTest({ name }) resolves a test whose `archived` property is true. Happens via the admin phase-switch endpoint on an archived test, or scripted automation iterating over all tests including archived ones.

Common situations: An admin UI list that does not filter out archived tests; a batch script advancing every test to release; a test archived earlier (e.g. after release) that someone tries to re-advance; stale local data where the test was archived by another operator.

Related errors


AI-assisted analysis of overleaf/overleaf@28ad3b03b7 (2026-09-03). Data as JSON: /api/errors/4f2ed4e7a13d4f22. Report an issue: GitHub.