derailed/k9s · error

expecting a context factory

Error message

expecting a context factory

What it means

podLogs is the shared helper behind TailLogs for DaemonSets, Jobs and similar workloads: it lists pods by the workload's selector using a watch factory pulled from the request context (internal/dao/ds.go:74). The ctx value under internal.KeyFactory must be dynamically a *watch.Factory; if the key is missing, nil, or holds a different Factory implementation, the assertion fails and 'expecting a context factory' is returned before any cluster call is made.

Source

Thrown at internal/dao/ds.go:74

// TailLogs tail logs for all pods represented by this DaemonSet.
func (d *DaemonSet) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
	ds, err := d.GetInstance(opts.Path)
	if err != nil {
		return nil, err
	}

	if ds.Spec.Selector == nil || len(ds.Spec.Selector.MatchLabels) == 0 {
		return nil, fmt.Errorf("no valid selector found on daemonset %q", opts.Path)
	}

	return podLogs(ctx, ds.Spec.Selector.MatchLabels, opts)
}

func podLogs(ctx context.Context, sel map[string]string, opts *LogOptions) ([]LogChan, error) {
	f, ok := ctx.Value(internal.KeyFactory).(*watch.Factory)
	if !ok {
		return nil, errors.New("expecting a context factory")
	}
	ls, err := metav1.ParseToLabelSelector(toSelector(sel))
	if err != nil {
		return nil, err
	}
	lsel, err := metav1.LabelSelectorAsSelector(ls)
	if err != nil {
		return nil, err
	}

	ns, _ := client.Namespaced(opts.Path)
	oo, err := f.List(client.PodGVR, ns, true, lsel)
	if err != nil {
		return nil, err
	}
	opts.MultiPods = true

	var po Pod

View on GitHub (pinned to 2d3ccc6ba2)

Solutions

  1. Inject the watch factory before tailing: ctx = context.WithValue(ctx, internal.KeyFactory, appFactory) where appFactory is the *watch.Factory instance.
  2. Route log tailing through the stock helpers (internal/model/log.go, internal/view/workload.go) which already seed KeyFactory.
  3. In tests, build a real *watch.Factory (as the dao test factories do) instead of a mock dao.Factory.
  4. Ensure the value's dynamic type is exactly *watch.Factory, not a wrapper implementing dao.Factory.

Example fix

// before
ctx := context.Background()
chans, err := dsDAO.TailLogs(ctx, opts) // -> expecting a context factory
// after
ctx := context.WithValue(context.Background(), internal.KeyFactory, appFactory) // *watch.Factory
chans, err := dsDAO.TailLogs(ctx, opts)
Defensive patterns

Strategy: type-guard

Validate before calling

f, ok := ctx.Value(internal.KeyFactory).(*watch.Factory)
if !ok || f == nil {
    return fmt.Errorf("log tailing requires a *watch.Factory in context")
}

Type guard

func factoryFrom(ctx context.Context) (*watch.Factory, bool) {
    f, ok := ctx.Value(internal.KeyFactory).(*watch.Factory)
    return f, ok && f != nil
}

Try / catch

if _, err := dsDAO.TailLogs(ctx, opts); err != nil && strings.Contains(err.Error(), "context factory") {
    ctx = context.WithValue(ctx, internal.KeyFactory, appFactory) // *watch.Factory
    _, err = dsDAO.TailLogs(ctx, opts)
}

Prevention

When it happens

Trigger: Invoking DaemonSet.TailLogs, Job.TailLogs (or podLogs directly) with a context that lacks internal.KeyFactory or carries a non-*watch.Factory value. The app normally injects *watch.Factory via model/log.go and view/workload.go; mock dao.Factory implementations (e.g. pegomock factories in tests) or embedding code passing context.Background() fail the assertion.

Common situations: Embedding k9s DAOs in another tool and calling TailLogs with a hand-built context; unit tests that use mock factories that do not implement or wrap *watch.Factory; refactors that replace the app factory with a wrapper type; calling log tailing before the app factory is initialized.

Related errors


AI-assisted analysis of derailed/k9s@2d3ccc6ba2 (2026-08-15). Data as JSON: /api/errors/131d5d2c30b8e529. Report an issue: GitHub.