actualbudget/actual · warning

key not found

Error message

key not found

What it means

DELETE /secrets/:name first validates the name against SecretName and responds HTTP 404 'key not found' when the name is not a known secret name. Note this endpoint conflates 'unknown name' with 'not found' — the 404 is returned before any permission or existence checks, so an unknown key is indistinguishable from a valid but absent key by status text alone.

Source

Thrown at packages/sync-server/src/app-secrets.js:89

      reason: 'not-admin',
      details: 'You have to be admin to manage global secrets',
    });
    return;
  }

  const secretFileId = perBudgetFile ? fileId : null;
  secretsService.set(name, value, secretFileId);

  res.status(200).send({ status: 'ok' });
});

app.delete('/:name', async (req, res) => {
  const name = req.params.name;
  const fileId = req.get('X-Actual-File-Id');
  const perBudgetFile = fileId != null;

  if (!(name in SecretName)) {
    res.status(404).send('key not found');
    return;
  }

  if (!perBudgetFile) {
    if (!canManageGlobalSecrets(res.locals.user_id)) {
      res.status(403).send({
        status: 'error',
        reason: 'not-admin',
        details: 'You have to be admin to manage global secrets',
      });
      return;
    }

    secretsService.reset(name);
    res.status(200).send({ status: 'ok' });
    return;
  }

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Use an exact SecretName value from the running server's secrets-service source.
  2. Check whether the key exists first with GET /secrets/:name to distinguish enum errors from absent keys.
  3. Fix URL encoding of the name path segment in your client.
  4. Align client and server versions so the secret name set matches.

Example fix

// before
delete('/secrets/gocardless_secet_id') // 404
// after
delete('/secrets/gocardless_secret_id') // 200 {status:'ok'}
Defensive patterns

Strategy: validation

Validate before calling

function isKnownSecretName(name) {
  return ['gocardless_secret_id','gocardless_account_id','pluggyai_client_id','pluggyai_client_secret'].includes(name);
}

Type guard

function isSecretName(name) {
  return typeof name === 'string' && name in SecretName;
}

Try / catch

if (!isKnownSecretName(name)) return; // skip, would 404
const res = await fetch(`/secrets/${encodeURIComponent(name)}`, { method: 'DELETE' });
if (res.status === 404) { /* name not in enum or key absent — log and continue */ }

Prevention

When it happens

Trigger: DELETE /secrets/<name> where <name> is misspelled or not in the SecretName enum; deleting a renamed provider secret name; URL-encoding issues mangling the name in the path; a client using a secret name from an older/newer server version.

Common situations: Cleanup scripts iterating over guessed key names; typos like 'gocardelss' in ops runbooks; version skew where the enum changed between deployments.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/889020984fa2ff06. Report an issue: GitHub.