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 PodView on GitHub (pinned to 2d3ccc6ba2)
Solutions
- Inject the watch factory before tailing: ctx = context.WithValue(ctx, internal.KeyFactory, appFactory) where appFactory is the *watch.Factory instance.
- Route log tailing through the stock helpers (internal/model/log.go, internal/view/workload.go) which already seed KeyFactory.
- In tests, build a real *watch.Factory (as the dao test factories do) instead of a mock dao.Factory.
- 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
- Never call TailLogs/podLogs with context.Background().
- Seed KeyFactory exactly as model/log.go and view/workload.go do, with dynamic type *watch.Factory.
- In tests, use a real *watch.Factory rather than a mock dao.Factory.
- Assert the factory out of the context once at the entry point and fail fast with a clear message.
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
- no factory in context
- expecting a job resource
- expecting a SubjectKind
- no context for gvr found
- expecting context Path
AI-assisted analysis of derailed/k9s@2d3ccc6ba2 (2026-08-15).
Data as JSON: /api/errors/131d5d2c30b8e529.
Report an issue: GitHub.