docker/compose · error

can't watch with action %q on service %s without a command

Error message

can't watch with action %q on service %s without a command

What it means

A `develop.watch` trigger uses `action: sync_exec` (or `exec`) but its `exec:` block has no `command`. Compose validates that sync_exec rules carry a command to run after syncing, and fails config load when `trigger.Exec.Command` is empty.

Source

Thrown at pkg/compose/watch.go:428

	for i, trigger := range config.Watch {
		if !filepath.IsAbs(trigger.Path) {
			trigger.Path = filepath.Join(baseDir, trigger.Path)
		}
		if p, err := filepath.EvalSymlinks(trigger.Path); err == nil {
			// this might fail because the path doesn't exist, etc.
			trigger.Path = p
		}
		trigger.Path = filepath.Clean(trigger.Path)
		if trigger.Path == "" {
			return nil, errors.New("watch rules MUST define a path")
		}

		if trigger.Action == types.WatchActionRebuild && service.Build == nil {
			return nil, fmt.Errorf("service %s doesn't have a build section, can't apply %s on watch", types.WatchActionRebuild, service.Name)
		}
		if trigger.Action == types.WatchActionSyncExec && len(trigger.Exec.Command) == 0 {
			return nil, fmt.Errorf("can't watch with action %q on service %s without a command", types.WatchActionSyncExec, service.Name)
		}

		config.Watch[i] = trigger
	}
	return &config, nil
}

func checkIfPathAlreadyBindMounted(watchPath string, volumes []types.ServiceVolumeConfig) bool {
	for _, volume := range volumes {
		if volume.Bind != nil {
			relPath, err := filepath.Rel(volume.Source, watchPath)
			if err == nil && !strings.HasPrefix(relPath, "..") {
				return true
			}
		}
	}
	return false
}

View on GitHub (pinned to ddc4b044b6)

Solutions

  1. Add an `exec: {command: [...]}` block to the sync_exec watch rule
  2. Use `action: sync` instead if no command should run after the copy

Example fix

# before
develop:
  watch:
    - path: ./src
      action: sync_exec
      target: /app/src

# after
develop:
  watch:
    - path: ./src
      action: sync_exec
      target: /app/src
      exec:
        command: make test
Defensive patterns

Strategy: validation

Validate before calling

# assert every sync_exec rule declares a command
python3 - <<'EOF'
import yaml, sys
p = yaml.safe_load(open('compose.yaml'))
for name, svc in (p.get('services') or {}).items():
    for rule in ((svc.get('develop') or {}).get('watch') or []):
        if rule.get('action') in ('sync_exec', 'exec') and not (rule.get('exec') or {}).get('command'):
            sys.exit(f"service {name}: sync_exec rule missing exec.command")
EOF

Prevention

When it happens

Trigger: Running `docker compose watch` / `up --watch` with a rule like `{path: ./src, action: sync_exec}` that omits `exec: {command: [...]}`. Raised in pkg/compose/watch.go during watch config validation.

Common situations: Renaming `sync` to `sync_exec` without adding an exec block; YAML indentation mistakes that leave `exec:` empty; misunderstanding that sync_exec requires a command while plain sync does not.

Related errors


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