docker/compose · error

--wait-timeout must be a non-negative integer

Error message

--wait-timeout must be a non-negative integer

What it means

Returned by validateFlags during `docker compose up` PreRunE when the --wait-timeout flag is negative. The flag is declared as an integer, so any value parses, but the validation function rejects values below zero because a timeout cannot be negative. This runs before any docker interaction, so it fails instantly at CLI-argument level.

Source

Thrown at cmd/compose/up.go:192

	flags.IntVar(&up.waitTimeout, "wait-timeout", 0, "Maximum duration in seconds to wait for the project to be running|healthy")
	flags.BoolVarP(&up.watch, "watch", "w", false, "Watch source code and rebuild/refresh containers when files are updated.")
	flags.BoolVar(&up.navigationMenu, "menu", false, "Enable interactive shortcuts when running attached. Incompatible with --detach. Can also be enable/disable by setting COMPOSE_MENU environment var.")
	flags.BoolVarP(&create.AssumeYes, "yes", "y", false, `Assume "yes" as answer to all prompts and run non-interactively`)
	flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
		// assumeYes was introduced by mistake as `--y`
		if name == "y" {
			logrus.Warn("--y is deprecated, please use --yes instead")
			name = "yes"
		}
		return pflag.NormalizedName(name)
	})
	return upCmd
}

//nolint:gocyclo
func validateFlags(up *upOptions, create *createOptions) error {
	if up.waitTimeout < 0 {
		return fmt.Errorf("--wait-timeout must be a non-negative integer")
	}
	if up.exitCodeFrom != "" && !up.cascadeFail {
		up.cascadeStop = true
	}
	if up.cascadeStop && up.cascadeFail {
		return fmt.Errorf("--abort-on-container-failure cannot be combined with --abort-on-container-exit")
	}
	if up.wait {
		if up.attachDependencies || up.cascadeStop || len(up.attach) > 0 {
			return fmt.Errorf("--wait cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies")
		}
		up.Detach = true
	}
	if create.Build && create.noBuild {
		return fmt.Errorf("--build and --no-build are incompatible")
	}
	if up.Detach && (up.attachDependencies || up.cascadeStop || up.cascadeFail || len(up.attach) > 0 || up.watch) {
		if up.wait {

View on GitHub (pinned to ddc4b044b6)

Solutions

  1. Pass zero or a positive integer: `docker compose up --wait --wait-timeout 60` (0 means no timeout).
  2. Fix the script computing the value: clamp with something like `MAX(0, computed)`.
  3. If you intended 'wait forever', omit --wait-timeout or pass 0.

Example fix

# before
TIMEOUT=$((END_TS - $(date +%s)))   # can go negative
docker compose up --wait --wait-timeout $TIMEOUT

# after
TIMEOUT=$((END_TS - $(date +%s))); (( TIMEOUT < 0 )) && TIMEOUT=0
docker compose up --wait --wait-timeout $TIMEOUT
Defensive patterns

Strategy: validation

Validate before calling

TIMEOUT=$(( ${WAIT_TIMEOUT:-60} )); (( TIMEOUT < 0 )) && TIMEOUT=0
docker compose up --wait --wait-timeout "$TIMEOUT"

Prevention

When it happens

Trigger: Passing `docker compose up --wait-timeout -1` (or any negative int); scripts computing the timeout arithmetically and passing a negative result when a base value is smaller than a subtraction; a stray '-' parsed as a value.

Common situations: CI scripts deriving --wait-timeout from a deadline calculation that can go negative (e.g. `END - NOW`); typos like `--wait-timeout -=1`; negative values used intentionally to mean 'no timeout' — not supported here.

Understand the failure class

Related errors


AI-assisted analysis of docker/compose@ddc4b044b6 (2026-08-15). Data as JSON: /api/errors/75f2f4e4336db5d0. Report an issue: GitHub.