Experimental work on try-claude established the intended architecture.
reiterate-claude was a failed experiment that destroyed good abstractions
(unused subclasses, flag-driven multicall, CompletionHook removed, ephemeral
submitter monkeypatch, class-hierarchy callers instead of Protocols). Do not
copy reiterate logic. At most skim it for accidental rename ideas; never treat
it as design authority.
Primary reference: try-claude / origin/try-claude-backup.
Decision: Split roles into focused modules. Prefer try-claude’s split, renamed for clarity if needed:
| try-claude | Target |
|---|---|
_hook_config.py |
_config.py |
_hook_markers.py |
_decorators.py |
_hook_callers.py |
_caller.py (callers + Protocol) + _implementation.py (HookImpl hierarchy) |
_callers.py |
_execution.py (multicall + CompletionHook orchestration) |
_project.py / _async.py |
same names |
_hooks.py remains a thin re-export/compat layer where useful.
Rejected: Anything from reiterate’s weakened _execution / _caller /
_implementation bodies.
Decision: HookCaller is a @runtime_checkable Protocol. Concrete
callers (NormalHookCaller, HistoricHookCaller, SubsetHookCaller)
structurally implement it. Use Protocols elsewhere for exec/monitoring
boundaries where try-claude did (_HookExec, CompletionHook, …).
Rejected: reiterate’s concrete inheritance hierarchy that erased the interface contract.
Why: Checkable protocols are how we type and isinstance-test callers without freezing a brittle base class. This is a critical design point, not optional polish.
Decision: Wrappers expose teardown as CompletionHook:
@runtime_checkable
class CompletionHook(Protocol):
def __call__(
self,
result: object | list[object] | None,
exception: BaseException | None,
) -> tuple[object | list[object] | None, BaseException | None]: ...WrapperImpl.setup_and_get_completion_hook(hook_name, caller_kwargs) runs
setup (next(gen)) and returns the completion closure. Old-style wrappers
are adapted inside that method via run_old_style_hookwrapper.
_multicall phases:
- Setup wrappers → collect
CompletionHooks - Run
NormalImpls (arg bind via_get_call_args) - Run completion hooks LIFO; each may replace
(result, exception) - Raise or return
Rejected: reiterate’s “CompletionHook no longer needed” adapter + flag dispatch inside multicall. That is the failed simplification.
Why: CompletionHook is the critical enhancement that simplifies the
inner hook engine: multicall orchestrates phases; wrappers own
setup/teardown; no .wrapper / .hookwrapper branching in the hot loop.
Decision: Live API uses configuration classes and impl subclasses:
HookspecConfiguration/HookimplConfiguration(__slots__+Final)HookImpl/NormalImpl/WrapperImplHookimplConfiguration.create_hookimpl(...) -> NormalImpl | WrapperImpl(fix try-claude footgun: normals must beNormalImpl, not bareHookImpl)- Typed split lists on
NormalHookCaller; dual-sequence_multicall
TypedDicts (HookspecOpts / HookimplOpts) are removed from the public
and internal live path. They are not “kept for compatibility.”
Pytest support shim only: a narrow compatibility helper (for pytest /
downstream that still hand-builds dict-shaped options during migration) may
accept mappings and convert them into configuration objects. That shim is
not the API; the API is the configuration classes. Do not re-export
TypedDicts as the preferred types in __all__.
Rejected: reiterate’s unused subclasses; keeping TypedDicts as the long-term dual API.
Decision: PluginManager owns a Submitter, threaded through
_hookexec / callers into _multicall. maybe_submit on awaitable normal
results; inactive = pass-through (await-me-maybe).
await pm.run_async(...) → Submitter.run. Optional pluggy[async] = ["greenlet"].
Rejected: reiterate’s ephemeral Submitter + _inner_hookexec monkeypatch.
Decision: Additive hub. Markers and PluginManager accept
str | ProjectSpec (try-claude).
Decision: Keep Result / TagTracer public APIs. Update monitoring /
_hookexec signatures for split lists, submitter, and CompletionHook-era
multicall. Tracing callbacks may receive a combined impl list for back-compat
of the before/after hook shapes (as try-claude did).
create_hookimpl→ returnNormalImplfor non-wrappers.Submitter.run→ sentinel instead ofif result is Nonefailure.- Normal list typed as
list[NormalImpl]. - Remove TypedDict definitions from the live API surface; isolate any dict acceptance in an explicit pytest/support shim module or helper.
git show try-claude:src/pluggy/_hook_callers.py
git show try-claude:src/pluggy/_callers.py
git show try-claude:src/pluggy/_hook_config.py
git show try-claude:src/pluggy/_async.py
git show try-claude:testing/test_async.py
git show try-claude:testing/test_hookcaller.py
git show try-claude:testing/test_project_spec.pyDo not use reiterate-claude as a logic source.