semaphoreui/semaphore · error
access key expired
Error message
access key expired
What it means
ErrAccessKeyExpired is returned by DeserializeSecret when the access key has an ExpireAt timestamp in the past. Expired secrets must never be usable, so any code path that materializes secret values (task dispatch, environment fill, remote job prep) fails with this sentinel so callers can distinguish expiry from other secret errors.
Solutions
- Extend or remove ExpireAt on the access key (or create a new key) and update tasks referencing it.
- Detect with errors.Is(err, ErrAccessKeyExpired) and surface a user-friendly 'key expired, run again' message (as the runner API does).
- Re-run the task after rotating the expired key.
- Set expiry policy on keys used by recurring tasks long enough to cover their schedule.
Example fix
// before
if err := encryptionService.DeserializeSecret(&key); err != nil { return err }
// after
if err := encryptionService.DeserializeSecret(&key); err != nil {
if errors.Is(err, server.ErrAccessKeyExpired) {
return fmt.Errorf("access key %q expired; please rotate it and re-run", key.Name)
}
return err
} Defensive patterns
Strategy: try-catch
Validate before calling
if key.ExpireAt != nil && time.Now().After(*key.ExpireAt) {
return fmt.Errorf("access key %q expired at %s", key.Name, key.ExpireAt)
} Type guard
func keyUsable(k db.AccessKey) bool { return k.ExpireAt == nil || time.Now().Before(*k.ExpireAt) } Try / catch
if err := encryptionService.DeserializeSecret(&key); err != nil {
if errors.Is(err, server.ErrAccessKeyExpired) {
tsk.Log("Survey secrets expired before the task started. Please run the task again.")
return
}
return err
} Prevention
- Set ExpireAt generously for keys used by scheduled tasks.
- Rotate expired keys proactively via monitoring on ExpireAt.
- Always check expiry before dispatching jobs that embed secrets.
When it happens
Trigger: DeserializeSecret(key) with key.ExpireAt != nil and tz.Now().After(*key.ExpireAt); reached from prepareRemoteJob, run, task survey secret gathering (api/runners/runners.go), and FillEnvironmentSecrets.
Common situations: Running a scheduled/old task whose access key TTL has elapsed; long-lived tasks re-reading secrets after the key expired; retrying a job that was queued past the key's expiry.
Related errors
- cannot modify secret in read-only storage
- vault storage id is required
- unsupported secret storage type
- secret does not belong to this environment
- invalid --ttl value
AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07).
Data as JSON: /api/errors/05577e9d93b6a794.
Report an issue: GitHub.
Appendix: source
Thrown at services/server/access_key_encryption_svc.go:22
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"github.com/semaphoreui/semaphore/db"
"github.com/semaphoreui/semaphore/pkg/common_errors"
"github.com/semaphoreui/semaphore/pkg/tz"
pro "github.com/semaphoreui/semaphore/pro/services/server"
)
const RekeyBatchSize = 100
var ErrReadOnlyStorage = errors.New("cannot modify secret in read-only storage")
// ErrAccessKeyExpired is returned when a key with ExpireAt in the past is
// deserialized. Expired secrets must never be usable.
var ErrAccessKeyExpired = errors.New("access key expired")
type AccessKeyEncryptionService interface {
SerializeSecret(key *db.AccessKey) error
DeserializeSecret(key *db.AccessKey) error
FillEnvironmentSecrets(env *db.Environment, deserializeSecret bool) error
DeleteSecret(key *db.AccessKey) error
RekeyAccessKeys(oldKey string) (err error)
// Task survey secrets: task-bound, expiring access keys
// (owner AccessKeyTaskSecret). See task_secret_svc.go.
CreateTaskSurveySecrets(projectID int, taskID int, secrets string, expireAt time.Time) error
GetTaskSurveySecrets(projectID int, taskID int) (string, error)
DeleteTaskSurveySecrets(projectID int, taskID int) error
}
func NewAccessKeyEncryptionService(
accessKeyRepo db.AccessKeyManager,
environmentRepo db.EnvironmentManager,View on GitHub (pinned to 1774ccb71a)