semaphoreui/semaphore · error
Empty token
Error message
Empty token
What it means
readRegistrationTokenFromFile panics when the registration-token file it reads is empty (len(tokenBytes) == 0). The runner needs a non-empty registration token to register with the server, and an empty file provides nothing to authenticate with.
Solutions
- Put the actual registration token into the file (no need for a trailing newline; it is trimmed)
- Regenerate a registration token from the server if you don't have one, and save it to the file
- Verify the file is non-empty before launch: `wc -c tokenfile`
Example fix
// before $ touch /etc/semaphore/registration.token // after $ echo "$REGISTRATION_TOKEN" > /etc/semaphore/registration.token
Defensive patterns
Strategy: validation
Validate before calling
[ -s /etc/semaphore/registration.token ] || { echo "registration token file is empty"; exit 1; }
semaphore runner register --registration-token-file /etc/semaphore/registration.token Prevention
- Use `[ -s file ]` to check non-empty token files before launch
- Fail fast in CI when secret mounts produce empty files
- Regenerate tokens if unsure rather than reusing placeholder files
- Trim/validate token contents when writing the file
When it happens
Trigger: Passing --registration-token-file (or config) pointing to a file that exists but has zero bytes — e.g. created by `touch` or truncated by a failed secret mount — triggering panic("Empty token").
Common situations: Kubernetes secret mounts not yet populated; CI pipelines creating placeholder files; accidental truncation when editing the token file.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- Cannot specify both --undo-to and --apply-to
- no admins found in database; create a admin first
- argument --login required
- user with login not found
- invalid --ttl value
AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07).
Data as JSON: /api/errors/a0bf2899a2da06c0.
Report an issue: GitHub.
Appendix: source
Thrown at cli/cmd/runner_register.go:43
func init() {
runnerRegisterCmd.PersistentFlags().BoolVar(&runnerRegisterArgs.stdinRegistrationToken, "stdin-registration-token", false, "Read registration token from stdin")
runnerRegisterCmd.PersistentFlags().StringVar(&runnerRegisterArgs.name, "name", "", "Runner name to register with")
runnerRegisterCmd.PersistentFlags().StringSliceVar(&runnerRegisterArgs.tags, "tags", nil, "Runner tags (comma-separated or repeat the flag)")
runnerRegisterCmd.PersistentFlags().StringVar(&runnerRegisterArgs.webhook, "webhook", "", "Runner webhook URL")
runnerRegisterCmd.PersistentFlags().StringVar(&runnerRegisterArgs.registrationTokenFilePath, "registration-token-file", "", "Read registration token from a file")
runnerRegisterCmd.PersistentFlags().BoolVar(&runnerRegisterArgs.enabled, "enabled", true, "Enable or disable the runner on the server")
runnerRegisterCmd.PersistentFlags().IntVar(&runnerRegisterArgs.projectID, "project-id", 0, "Project ID for project-level runner (global runner if not provided)")
runnerCmd.AddCommand(runnerRegisterCmd)
}
func readRegistrationTokenFromFile(path string) {
tokenBytes, err := os.ReadFile(path)
if err != nil {
panic(err)
}
if len(tokenBytes) == 0 {
panic("Empty token")
}
util.Config.Runner.RegistrationToken = strings.TrimSpace(string(tokenBytes))
}
func initRunnerRegistrationToken() {
if runnerRegisterArgs.registrationTokenFilePath != "" {
readRegistrationTokenFromFile(runnerRegisterArgs.registrationTokenFilePath)
return
}
if util.Config.Runner.RegistrationTokenFile != "" {
readRegistrationTokenFromFile(util.Config.Runner.RegistrationTokenFile)
return
}
if !runnerRegisterArgs.stdinRegistrationToken {
returnView on GitHub (pinned to 1774ccb71a)