{"record":{"id":"b9d56cc35c096746","repo":"elsa-workflows/elsa-core","slug":"an-alteration-job-with-id-job-id-already-exists-and-is-not","errorCode":null,"errorMessage":"An alteration job with ID '{job.Id}' already exists and is not visible to the current tenant.","messagePattern":"An alteration job with ID '(.+?)' already exists and is not visible to the current tenant\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.Alterations.Core/Stores/MemoryAlterationJobStore.cs","lineNumber":109,"sourceCode":"\n    /// <remarks>\n    /// Ambient tenant is applied here rather than in <see cref=\"AlterationJobFilter.Apply\"/>.\n    /// EF owns that via <c>SetTenantIdFilter</c>; Memory must compensate.\n    /// </remarks>\n    private IQueryable<AlterationJob> Filter(IQueryable<AlterationJob> query, AlterationJobFilter filter) =>\n        filter.Apply(query.WhereVisibleToTenant(CurrentTenantId));\n\n    private string CurrentTenantId => _tenantAccessor?.TenantId ?? Tenant.DefaultTenantId;\n\n    private bool IsVisible(Entity entity) => TenantVisibility.IsVisible(entity.TenantId, CurrentTenantId);\n\n    private void EnsureIdAvailable(AlterationJob job)\n    {\n        var existing = _store.Find(x => x.Id == job.Id);\n\n        if (existing is not null && !CanReplace(existing))\n        {\n            throw new InvalidOperationException(\n                $\"An alteration job with ID '{job.Id}' already exists and is not visible to the current tenant.\");\n        }\n    }\n\n    /// <summary>\n    /// <c>*</c> is visible to every tenant, but only an agnostic writer may replace it.\n    /// Named tenants may upsert their own visible rows.\n    /// </summary>\n    private bool CanReplace(Entity existing) =>\n        existing.TenantId == Tenant.AgnosticTenantId\n            ? CurrentTenantId == Tenant.AgnosticTenantId\n            : IsVisible(existing);\n\n    private void ApplyCurrentTenant(Entity entity)\n    {\n        if (entity.TenantId == Tenant.AgnosticTenantId || _tenantAccessor is null)\n            return;\n","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.Alterations.Core/Stores/MemoryAlterationJobStore.cs#L91-L127","documentation":"MemoryAlterationJobStore.EnsureIdAvailable throws when saving an alteration job whose ID already exists in the in-memory store but cannot be replaced because the existing entry is not visible to the current tenant. Tenant-agnostic entries ('*') are visible to everyone but only an agnostic writer may replace them, so a tenant-scoped writer colliding with such an entry (or another tenant's entry) triggers this InvalidOperationException.","triggerScenarios":"Calling SaveAsync or SaveManyAsync with an AlterationJob whose Id matches an existing job that CanReplace(existing) evaluates false for (existing belongs to a different tenant, or existing is tenant-agnostic and the writer is tenant-scoped).","commonSituations":"Re-running job creation with a client-supplied or persisted deterministic ID after a tenant context change; seeding multi-tenant data where one tenant's ID collides with an agnostic record; tests reusing fixed job IDs across tenant scopes.","solutions":["Generate a new unique job ID instead of reusing the conflicting one.","Ensure the tenant context matches the existing record's tenant when an update is intended.","If replacing an agnostic ('*') record is intended, perform the write with a tenant-agnostic writer context."],"exampleFix":"// before\nvar job = new AlterationJob { Id = fixedId, TenantId = currentTenant };\nawait jobStore.SaveAsync(job, ct);\n\n// after\nvar job = new AlterationJob { Id = Guid.NewGuid().ToString(), TenantId = currentTenant };\nawait jobStore.SaveAsync(job, ct);","handlingStrategy":"try-catch","validationCode":"var existing = await jobStore.FindAsync(new AlterationJobFilter { Id = job.Id }, ct);\nif (existing is not null && existing.TenantId != job.TenantId)\n    throw new InvalidOperationException($\"Job ID '{job.Id}' already exists in another tenant scope; use a new ID.\");","typeGuard":null,"tryCatchPattern":"try\n{\n    await jobStore.SaveAsync(job, ct);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"already exists\"))\n{\n    job.Id = Guid.NewGuid().ToString();\n    await jobStore.SaveAsync(job, ct);\n}","preventionTips":["Prefer store-generated GUIDs over client-supplied job IDs.","Keep tenant scope consistent for the lifetime of a save operation.","In multi-tenant tests, derive IDs per tenant rather than sharing constants."],"tags":["in-memory-store","tenancy","id-conflict","alterations"],"backgroundTag":"file-already-exists","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}