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

  1. Put the actual registration token into the file (no need for a trailing newline; it is trimmed)
  2. Regenerate a registration token from the server if you don't have one, and save it to the file
  3. 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

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


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 {
		return

View on GitHub (pinned to 1774ccb71a)