derailed/k9s · error

no factory in context

Error message

no factory in context

What it means

Pod.TailLogs pulls the watch factory out of the request context before it can even fetch the pod: the value under internal.KeyFactory must be dynamically a *watch.Factory (internal/dao/pod.go:216). If the key is missing, nil, or holds a different Factory implementation, the assertion fails and 'no factory in context' is returned immediately. The app normally seeds this via model/log.go and view/workload.go, whose factory is *watch.Factory.

Source

Thrown at internal/dao/pod.go:216

	o, err := p.getFactory().Get(p.gvr, fqn, true, labels.Everything())
	if err != nil {
		return nil, err
	}

	var pod v1.Pod
	err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod)
	if err != nil {
		return nil, err
	}

	return &pod, nil
}

// TailLogs tails a given container logs.
func (p *Pod) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
	fac, ok := ctx.Value(internal.KeyFactory).(*watch.Factory)
	if !ok {
		return nil, errors.New("no factory in context")
	}
	o, err := fac.Get(p.gvr, opts.Path, true, labels.Everything())
	if err != nil {
		return nil, err
	}
	var po v1.Pod
	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po); err != nil {
		return nil, err
	}
	coCounts := len(po.Spec.InitContainers) + len(po.Spec.Containers) + len(po.Spec.EphemeralContainers)
	if coCounts == 1 {
		opts.SingleContainer = true
	}

	outs := make([]LogChan, 0, coCounts)
	if co, ok := GetDefaultContainer(&po.ObjectMeta, &po.Spec); ok && !opts.AllContainers {
		opts.DefaultContainer = co
		return append(outs, tailLogs(ctx, p, opts)), nil

View on GitHub (pinned to 2d3ccc6ba2)

Solutions

  1. Seed the key before tailing: ctx = context.WithValue(ctx, internal.KeyFactory, appFactory) with appFactory of dynamic type *watch.Factory.
  2. Route log tailing through internal/model/log.go or internal/view/workload.go which already inject KeyFactory.
  3. In tests, build a real *watch.Factory as the dao test helpers do instead of a mock Factory.
  4. Make sure the value stored is the concrete *watch.Factory, not a dao.Factory wrapper.

Example fix

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

Strategy: type-guard

Validate before calling

f, ok := ctx.Value(internal.KeyFactory).(*watch.Factory)
if !ok || f == nil {
    ctx = context.WithValue(ctx, internal.KeyFactory, appFactory) // *watch.Factory
}
chans, err := podDAO.TailLogs(ctx, opts)

Type guard

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

Try / catch

if _, err := podDAO.TailLogs(ctx, opts); err != nil && strings.Contains(err.Error(), "no factory in context") {
    return errors.New("log tailing requires *watch.Factory; seed internal.KeyFactory")
}

Prevention

When it happens

Trigger: Calling Pod.TailLogs with a context lacking internal.KeyFactory or holding a non-*watch.Factory value: embedding code passing context.Background(), unit tests using mock dao.Factory implementations, refactors wrapping the factory in another type, or log tailing invoked before the app factory is initialized. It fails before the pod lookup, so the path is irrelevant.

Common situations: Embedding k9s DAOs in a custom tool; tests with pegomock factories that do not wrap *watch.Factory; views refactored to build their own contexts; nil app factory during startup races.

Related errors


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