GoogleContainerTools/skaffold · error

unable to start trigger: %w

Error message

unable to start trigger: %w

What it means

SkaffoldListener.WatchForChanges starts the change trigger (manual/polling/notify) that gates the dev loop. If trigger.StartTrigger fails, the listener returns 'unable to start trigger'. The trigger is what decides when the dev loop re-runs (file events, polling interval, or manual Enter).

Source

Thrown at pkg/skaffold/runner/listen.go:64

type SkaffoldListener struct {
	Monitor                 filemon.Monitor
	Trigger                 trigger.Trigger
	sourceDependenciesCache graph.SourceDependenciesCache
	intentChan              <-chan bool
}

func (l *SkaffoldListener) LogWatchToUser(out io.Writer) {
	l.Trigger.LogWatchToUser(out)
}

// WatchForChanges listens to a trigger, and when one is received, computes file changes and
// conditionally runs the dev loop.
func (l *SkaffoldListener) WatchForChanges(ctx context.Context, out io.Writer, devLoop func() error) error {
	ctxTrigger, cancelTrigger := context.WithCancel(ctx)
	defer cancelTrigger()
	trigger, err := trigger.StartTrigger(ctxTrigger, l.Trigger)
	if err != nil {
		return fmt.Errorf("unable to start trigger: %w", err)
	}

	// exit if file monitor fails the first time
	if err := l.Monitor.Run(l.Trigger.Debounce()); err != nil {
		return fmt.Errorf("failed to monitor files: %w", err)
	}

	l.LogWatchToUser(out)

	for {
		select {
		case <-ctx.Done():
			return nil
		case <-l.intentChan:
			if err := l.do(devLoop); err != nil {
				return err
			}
		case <-trigger:

View on GitHub (pinned to a1189de023)

Solutions

  1. Fall back to polling: run with `--trigger=poll` (and a sane `--poll-interval`, e.g. 1s)
  2. Fix any invalid trigger flags (e.g. non-numeric --poll-interval)
  3. Raise inotify limits if watcher creation fails (`fs.inotify.max_user_watches/instances`)
  4. Run with --trigger=manual to isolate whether the trigger subsystem is the failure

Example fix

// before
$ skaffold dev   # notify trigger fails on network FS
// after
$ skaffold dev --trigger=poll --poll-interval=1000
Defensive patterns

Strategy: fallback

Validate before calling

// prefer polling trigger on filesystems without notify support
trigger := "notify"
if onNetworkFilesystem(cwd) { trigger = "poll" }
args := []string{"dev", "--trigger=" + trigger, "--poll-interval=1000"}

Type guard

func isTriggerStartErr(err error) bool {
    return err != nil && strings.Contains(err.Error(), "unable to start trigger")
}

Try / catch

if err := listener.WatchForChanges(ctx, out, devLoop); isTriggerStartErr(err) {
    log.Println("notify trigger failed; retrying with poll trigger")
    l.Trigger = pollingTrigger
    return listener.WatchForChanges(ctx, out, devLoop)
}

Prevention

When it happens

Trigger: `trigger.StartTrigger(ctxTrigger, l.Trigger)` returns an error — e.g. the polling trigger cannot initialize its poller, the notify trigger cannot create a file watcher, or trigger configuration (interval, mode) is invalid.

Common situations: Filesystem doesn't support notify-based watching (network mounts, containers with limited syscalls) while --trigger=notify is default; invalid --poll-interval value; watch resource exhaustion when creating the trigger's watcher.

Related errors


AI-assisted analysis of GoogleContainerTools/skaffold@a1189de023 (2026-09-05). Data as JSON: /api/errors/eeae80698fba091b. Report an issue: GitHub.