medusajs/medusa · error · Error
Key ${key} already exists in app metadata
Error message
Key ${key} already exists in app metadata What it means
After resolving the flow, the Redis engine's cancel() fetches the running transaction. If no transaction with that transactionId exists for the workflow, it throws 'Transaction not found' (a transaction that already completed or never existed falls into this path when the graceful 'exists:false' branch doesn't apply).
Source
Thrown at packages/core/core-flows/src/auth/steps/set-auth-app-metadata.ts:57
* authIdentityId: "au_1234",
* actorType: "user", // or `customer`, or custom type
* value: null
* })
* ```
*/
export const setAuthAppMetadataStep = createStep(
setAuthAppMetadataStepId,
async (data: SetAuthAppMetadataStepInput, { container }) => {
const service = container.resolve<IAuthModuleService>(Modules.AUTH)
const key = `${data.actorType}_id`
const authIdentity = await service.retrieveAuthIdentity(data.authIdentityId)
const appMetadata = authIdentity.app_metadata || {}
// If the value is null, we are deleting the association with an actor
if (isDefined(appMetadata[key]) && data.value !== null) {
throw new Error(`Key ${key} already exists in app metadata`)
}
const oldValue = appMetadata[key]
appMetadata[key] = data.value
await service.updateAuthIdentities({
id: authIdentity.id,
app_metadata: appMetadata,
})
return new StepResponse(authIdentity, {
id: authIdentity.id,
key: key,
value: data.value,
oldValue,
})
},
async (idAndKeyAndValue, { container }) => {View on GitHub (pinned to 5e06e544a2)
Solutions
- Treat this error as idempotent success in handlers (transaction already gone)
- Double-check transactionId and workflowId pairing before cancelling
- Catch and log-and-ignore when the goal is merely ensuring the transaction is stopped
Example fix
// before
await engine.cancel(wfId, txId)
// after
try { await engine.cancel(wfId, txId) } catch (e) { if (e.message === 'Transaction not found') return { idempotent: true } throw e } Defensive patterns
Strategy: try-catch
Validate before calling
const tx = await engine.getRunningTransaction(workflowId, transactionId).catch(() => null)
if (!tx) return { alreadyGone: true }
await engine.cancel(workflowId, transactionId) Try / catch
try { await engine.cancel(wf, tx) } catch (e) { if (e.message === 'Transaction not found') return { idempotent: true }; throw e } Prevention
- Design cancel handlers to be idempotent against vanished transactions
- Check transaction state before cancelling to avoid races
When it happens
Trigger: Cancelling a transactionId that already finished, was cancelled, expired from Redis, or belongs to a different workflow id.
Common situations: Race conditions where the workflow completes while a cancel request is in flight; Redis TTL/eviction removing transaction state; replaying cancel requests from a queue.
Related errors
- --config file must be of type .json or .yaml - ${configFileC
- --paths must be a directory - ${additionalPath}
- --base must be a file - ${baseFile}
- invalid_data
- invalid_data
AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27).
Data as JSON: /api/errors/09254b97f0c09c85.
Report an issue: GitHub.