semaphoreui/semaphore · error
secret does not belong to this environment
Error message
secret does not belong to this environment
What it means
In updateEnvironmentSecrets (api/projects/environment.go:93), during the DELETE branch, each requested secret key is loaded and checked: if key.EnvironmentID is nil or differs from the environment being updated, the function appends 'secret does not belong to this environment' and skips deletion. This guards against removing a secret key through an environment that doesn't own it.
Solutions
- Remove that key from the delete list for this environment call, or issue the delete against the environment that owns the key.
- Query the secret's EnvironmentID (project access keys) and confirm it matches the target env.ID before requesting deletion.
- For legacy keys with nil EnvironmentID, update the row to set the correct environment, then retry.
- Verify via the API which environment each secret key belongs to before batch operations.
Example fix
// before: deleting a key from the wrong env
{"secrets": {"delete": ["DB_PASSWORD"]}} // key owned by 'production'
// after: only delete keys owned by this environment
{"secrets": {"delete": ["STAGING_DB_PASSWORD"]}} Defensive patterns
Strategy: validation
Validate before calling
// verify ownership before requesting deletion
const key = await getAccessKey(env.projectId, keyName);
if (key == null || key.environment_id !== env.id) {
throw new Error(`key ${keyName} is not owned by environment ${env.id}`);
} Type guard
function keyBelongsToEnv(key, env) {
return key != null && typeof key.environment_id === "number" &&
key.environment_id === env.id;
} Try / catch
try {
await updateEnvironment(env.id, { secrets: { delete: [keyName] } });
} catch (e) {
if (/secret does not belong to this environment/.test(e.message)) {
// retry against the owning environment
}
} Prevention
- Always scope secret operations to the environment that created the key
- Fetch fresh key metadata instead of caching key ids across environments
- Audit legacy rows with NULL environment_id and assign them
- Avoid pasting secret lists between environment payloads
When it happens
Trigger: Calling UpdateEnvironment or AddEnvironment with a secrets delete list containing a key whose AccessKey record has EnvironmentID nil or pointing to a different environment in the same project — e.g. deleting a key that was created under environment 'production' while updating environment 'staging'.
Common situations: Merged/pasted secret name lists across environments; API clients caching key IDs from another environment; keys created before environment scoping existed (nil EnvironmentID legacy rows); test TestUpdateEnvironmentSecrets_DeleteRejectsKeyFromOtherEnvironment exercises exactly this.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- missing secret
- invalid environment secret type
- must be valid JSON
- key can not be empty
- values must be scalar
AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07).
Data as JSON: /api/errors/0e02b31b3dd74673.
Report an issue: GitHub.
Appendix: source
Thrown at api/projects/environment.go:93
SourceStorageID: env.SecretStorageID,
SourceStorageKey: sourceStorageKey,
SourceStorageType: storageType,
})
if err != nil {
errors = append(errors, err)
continue
}
case db.EnvironmentSecretDelete:
key, err = c.accessKeyRepo.GetAccessKey(env.ProjectID, secret.ID)
if err != nil {
errors = append(errors, err)
continue
}
if key.EnvironmentID == nil || *key.EnvironmentID != env.ID {
errors = append(errors, fmt.Errorf("secret does not belong to this environment"))
continue
}
err = c.accessKeyService.Delete(env.ProjectID, secret.ID)
if err != nil {
errors = append(errors, err)
continue
}
case db.EnvironmentSecretUpdate:
key, err = c.accessKeyRepo.GetAccessKey(env.ProjectID, secret.ID)
if err != nil {
errors = append(errors, err)
continue
}
if key.EnvironmentID == nil || *key.EnvironmentID != env.ID {
errors = append(errors, fmt.Errorf("secret does not belong to this environment"))View on GitHub (pinned to 1774ccb71a)