deepseek-ai/deepseek-harness · error

sessions.provide: missing prop "${name}"

Error message

sessions.provide: missing prop "${name}"

What it means

Thrown while materializing a session's standard-props bundle (SessionProvideChannel.materializeInfo): a provider descriptor declared a prop name in its props roster, but its resolve(binding) contribution did not include that key. The provide channel is fail-loud by design — the declared roster and the resolver's actual output must agree exactly for every session binding — so a missing member aborts materialization instead of silently yielding an undefined prop. Materialization runs from the constructor, from record when a session binding arrives, and from provideInfo when a roster change rebuilds live bundles (a failing provide() registration rolls back and rethrows).

Source

Thrown at packages/client/runtime/src/client/sessions/provide.ts:152

      const contributedProps = contribution.props ?? {}
      for (const name of Object.keys(contributedHooks)) {
        if (!(descriptor.hooks ?? []).includes(name)) {
          throw new Error(`sessions.provide: undeclared hook "${name}"`)
        }
      }
      for (const name of Object.keys(contributedProps)) {
        if (!(descriptor.props ?? []).includes(name)) {
          throw new Error(`sessions.provide: undeclared prop "${name}"`)
        }
      }
      for (const name of descriptor.hooks ?? []) {
        const source = contributedHooks[name]
        if (source === undefined) throw new Error(`sessions.provide: missing hook "${name}"`)
        if (Object.hasOwn(hooks, name)) throw new Error(`sessions.provide: duplicate hook "${name}"`)
        hooks[name] = source
      }
      for (const name of descriptor.props ?? []) {
        if (!Object.hasOwn(contributedProps, name)) throw new Error(`sessions.provide: missing prop "${name}"`)
        if (Object.hasOwn(props, name)) throw new Error(`sessions.provide: duplicate prop "${name}"`)
        props[name] = contributedProps[name]
      }
    }
    return {
      sessionId: binding.sessionId,
      hooks,
      props,
      // The useProjection seat: key-addressed bare value faces off the
      // session's projection store (open key space — never a static roster member).
      projections: { faceOf: key => binding.session.projections.faceOf(key) },
    }
  }

  /** Rebuild the static projection and the owner's live bundles, then republish the current one. */
  private applyRosterChange(): void {
    this.maybeInfoCache = this.materializeMaybeInfo()
    this.host.rebuildBundles()

View on GitHub (pinned to b150a551b8)

Solutions

  1. Make resolve(binding) return every name listed in descriptor.props for every binding — roster members must be unconditional values
  2. If the value genuinely may be absent, remove the name from descriptor.props (without a session every key already reads absent) or model it through the open projections key space instead of a roster member
  3. Check for typos and casing drift between the descriptor array and the resolver's returned object keys
  4. Add a unit test that runs the resolver against a representative binding and asserts Object.keys(contribution.props) matches descriptor.props

Example fix

// before — declared prop never contributed
sessions.provide({
  props: ['workspaceTitle'],
  resolve: (binding) => ({ hooks: { session: binding.session } }),
})

// after — every declared member is returned on every binding
sessions.provide({
  props: ['workspaceTitle'],
  resolve: (binding) => ({
    props: { workspaceTitle: titleOf(binding) },
  }),
})
Defensive patterns

Strategy: validation

Validate before calling

// registration-time smoke check: the resolver must cover the declared roster
function assertResolverCoversDescriptor(
  descriptor: SessionProvideDescriptor,
  sample: SessionBinding,
): void {
  const contribution = descriptor.resolve(sample)
  for (const name of descriptor.props ?? []) {
    if (!Object.hasOwn(contribution.props ?? {}, name)) {
      throw new Error(`provider would fail materialization: missing prop ${name}`)
    }
  }
}

Try / catch

try {
  const disposer = sessions.provide(descriptor)
} catch (error) {
  // the channel already rolled the registration back; fix the descriptor/resolver mismatch, not the call site
}

Prevention

When it happens

Trigger: Registering sessions.provide({ props: ['x'], resolve }) whose resolve returns an object whose props omits 'x' — e.g. a conditional spread ...(cond ? { props: { x } } : {}), or a rename applied to the resolver but not the descriptor. The throw fires at the next materialization of any session bundle under that roster.

Common situations: Writing a new sessions.provide plugin and letting the declaration drift from the resolver; renaming a prop in one place during a refactor; returning hooks but forgetting props; fixture-driven test doubles that construct contributions by hand.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


AI-assisted analysis of deepseek-ai/deepseek-harness@b150a551b8 (2026-08-24). Data as JSON: /api/errors/91de9212365b7128. Report an issue: GitHub.