Skip to content

Commit d13787b

Browse files
timsaucerclaude
andcommitted
Say which codec chains each extension hook's context carries
`__datafusion_session_planner__` was documented as receiving a context that carries the final codec chains, and `MyPlannerExtension` relies on exactly that when it takes the host's codecs off `ctx` instead of minting its own. The other side was never stated: `__datafusion_session_extension__` runs before anything is installed, so its `ctx` is the same session with the chains the receiver already had — missing this call's codecs, including the bundle's own. Both hooks hand back a valid task-context provider, which is what components actually need, so the difference only bites an author who reads codec chains off the context. Recorded on `SessionExtensionExportable`, in the FFI guide's two-phase section, and in Rule 2 of the capsule-protocol skill. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 0712eff commit d13787b

3 files changed

Lines changed: 24 additions & 0 deletions

File tree

‎.ai/skills/ffi-capsule-protocol/SKILL.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ Wrap `fallback` and delegate to it; returning a planner that ignores it discards
7070
every layer beneath, including one the session already had. It runs after every
7171
bundle's codecs are installed, so `ctx` carries the final chains.
7272

73+
That is also the only hook where it does. `__datafusion_session_extension__`
74+
runs before anything is installed, so its `ctx` still carries the chains the
75+
receiver had — the same session, and the same task-context provider, but not
76+
this call's codecs, not even your own. Read the host's codec chains in the
77+
planner hook, never in the extension hook.
78+
7379
A *codec* must always be handed over as an object implementing its getter, never
7480
as the bare capsule the getter returns; `with_extensions` refuses a capsule.
7581
A codec's wire id — the string a payload names on decode, which has to mean the

‎docs/source/contributor-guide/ffi.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,15 @@ handing each the planner built so far. Two consequences worth holding onto:
406406
deep](#rebinding-a-planners-codecs-is-one-level-deep)), so a fallback captured before
407407
the codecs were complete would stay stale forever.
408408

409+
The two hooks therefore see the same session through different chains. Both receive a
410+
handle on the one session, so the task-context provider taken off either is the same and
411+
stays valid — but the `ctx` in phase one still carries the chains the receiver had, since
412+
nothing is installed yet, while the `ctx` in phase two carries every bundle's codecs. A
413+
bundle that reads the host's codec chains — `MyPlannerExtension` does, to give its
414+
planner the host's codecs rather than minting its own — must do that in the planner hook.
415+
Reading them in phase one gets the chains from before the call, missing even the bundle's
416+
own codecs.
417+
409418
An extension that ignores `fallback` and returns an unrelated planner replaces every
410419
layer beneath it, including any planner the session already had. That is legal — a
411420
library that must be the only planner does it deliberately — but it is not composable,

‎python/datafusion/extensions.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,15 @@ class SessionExtensionExportable(Protocol):
231231
mutating the context they are handed — a registration made during binding
232232
is not rolled back if a later extension fails.
233233
234+
``ctx`` is the right session but not yet the final codec chains: this hook
235+
runs before anything is installed, so ``ctx`` still carries whatever chains
236+
the receiver had. Take the task-context provider off it — that is bound to
237+
the session and is what the components need — but do not read its codec
238+
chains expecting to find this call's codecs, including your own.
239+
:py:class:`SessionPlannerExportable` is the hook that sees the completed
240+
chains, which is why a planner that wraps the host's codecs builds them
241+
there rather than here.
242+
234243
A bundle that also contributes a query planner implements
235244
:py:class:`SessionPlannerExportable` alongside this protocol. Planners are
236245
installed in a second phase, so they are not part of the components

0 commit comments

Comments
 (0)