12 KiB
Verificare "function hooks" — Claude Code 2.1.274 (nativ, win32-x64)
Sursa de verificat: https://claudefa.st/blog/tools/hooks/function-hooks (blog tert, neoficial).
Metoda: (1) incercare de rulare reala /plugin-types; (2) cautare de siruri si context in
binarul nativ instalat, C:\Users\mmari\.local\bin\claude.exe (233691808 octeti, Bun-compiled,
comitul 1efcc1361e64). Fiecare constatare de mai jos e marcata cu sursa ei.
1. Rularea comenzii cerute
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude -p "/plugin-types ./types"
Rezultat: nu s-a generat niciun fisier. Raspunsul modelului a fost text conversational
("Ready — no task given yet." / la reincercare cu --output-format json: "I'm ready. What would you like me to work on?"). Niciun folder types/ sau .claude/types/ nu a aparut in
scratchpad dupa rulare (verificat cu ls).
Cauza identificata, nu specifica function hooks: am testat control cu claude -p "/help"
(comanda locala cea mai de baza din tot CLI-ul, intotdeauna inregistrata, fara nicio poarta de
feature-flag). A esuat identic — modelul a primit "/help" ca text mangled si a raspuns
conversational, in loc sa afiseze ajutorul. Concluzie: invocarea claude -p "<text>" din acest
mediu nu ruteaza deloc prin dispecerul de comenzi locale ("slash commands") — cererea merge
direct la model ca prompt text. Din --debug-file, header-ul de atribuire arata
cc_entrypoint=sdk-cli, adica sesiunea porneste prin calea SDK, nu prin REPL-ul interactiv complet;
acolo comenzile locale par sa nu fie interceptate. Deci esecul lui /plugin-types nu e dovada
ca function hooks sunt dezactivate — e o limitare a modului de invocare folosit aici, valabila
si pentru comenzi complet neutre ca /help. N-am putut testa varianta interactiva (fara TTY in
acest mediu).
claude --help si claude plugin --help (rulate separat, capturate integral) nu listeaza
nicio comanda plugin-types la nivel de CLI top-level — motivul e ca /plugin-types e o comanda
in-sesiune ("slash command"), nu o subcomanda a executabilului claude, deci absenta ei din
--help e normala si nu spune nimic despre activare.
2. Ce exista REAL in binar (cautare de siruri, cu offset de octet verificabil)
Nu s-au generat .d.ts, deci n-am putut citi declaratii TypeScript complete ca "dovada
canonica" ceruta in sarcina. In schimb, am gasit in binar cod sursa minificat (nu doar siruri
izolate) care confirma mecanismul de facto. Tot ce urmeaza e citat verbatim din binar, cu offset.
2a. Flag-ul si poarta lui reala
La offset ~103270720 (grep -a -b), blocul de cod al flag-ului:
var kYe="tengu_plugin_hooks_modules";
var Azt=()=>!1;
var AFe=()=>a.CLAUDE_CODE_ENABLE_FUNCTION_HOOKS??P(kYe,Azt());
var h8=()=>AFe()&&!qb()&&!Br("hooks")&&!zm();
Interpretare directa din cod:
- flag-ul GrowthBook real se numeste
tengu_plugin_hooks_modules, default oprit (Azt=()=>!1). CLAUDE_CODE_ENABLE_FUNCTION_HOOKSe o suprascriere locala peste acel flag (??) — confirma exact ce zice blogul despre numele variabilei de mediu.- dar poarta finala folosita in productie,
h8(), cere si!qb() && !Br("hooks") && !zm()— trei conditii suplimentare. Numele acestor functii sunt minificate si reciclate in zeci de module din bundle (acelasi numeqb/Br/zmapare cu corpuri complet diferite in alte scope-uri), deci nu pot atribui cu certitudine ce verifica exact aici fara deminificare completa — onest: aceasta e limita cercetarii, nu o concluzie. - Alaturi, la acelasi offset, un tabel de atribuire a sursei flag-ului pentru afisare in UI:
{override:"from a local override", payload:"from GrowthBook (this session's payload)", disk:"from GrowthBook (the disk cache of an earlier session)", disabled:"from the default (GrowthBook is off ...)"}— confirma ca e un flag GrowthBook cu override local, exact mecanismul descris de blog.
2b. Comanda /plugin-types — confirmata REAL, dar NEgatata de flag
La offset 203438732, definitia completa a comenzii (citat verbatim):
var mer=Object.freeze({type:"local",name:"plugin-types",
description:"Write claude-code.d.ts, claude-code-plugins.d.ts and claude-code-mcp.d.ts:
the plugin API's TypeScript declarations, the enabled plugins' type contracts and the
inputs of the connected MCP tools, for typing a hooks module against this session",
argumentHint:"[dir]", supportsNonInteractive:!0,
load:()=>import("B:/~BUN/root/chunk-0rpsm23r.js")});
Important: acest obiect n-are nicio conditie isEnabled/gate legata de h8() sau de
flag — e inregistrata neconditionat. Descrierea confirma explicit sintagma "hooks module"
din blog: comanda exista ca sa tipizeze "a hooks module against this session". Deci comanda in
sine e reala si documentata intern, indiferent de starea flag-ului; problema empirica de mai
sus (sectiunea 1) e doar despre modul de invocare -p din acest mediu.
2c. Lista REALA de evenimente si API-uri ("scan manifest"), gasita ca date, nu ca documentatie
La offset 223160560 exista un manifest static de tip "scan" folosit (aparent) pentru analiza statica / sandboxing a unui modul de plugin, cu lista completa si explicita:
scan: {
hooks: ["session.start","ui.render","command.run","ui.close","ui.focus","ui.scroll",
"tool.call","prompt.submit"],
calls: ["clock.after","clock.every","clock.now","command.register","fs.list","fs.read",
"fs.stat","process.run","session.id","session.messages","store.get","store.set",
"telemetry.log","telemetry.mark","ui.close","ui.invalidate","ui.log","ui.open",
"ui.resolve","ui.status"]
}
Aceasta e cea mai tare dovada gasita — e o lista de date, nu un string de proza, deci foarte probabil chiar suprafata reala (sau foarte apropiata) a API-ului de hooks module:
- evenimente de hook confirmate:
session.start,ui.render,command.run,ui.close,ui.focus,ui.scroll,tool.call,prompt.submit(8 la numar). turn.completesiturn.startNU sunt in lista de evenimente de hook, desi cele doua siruri exista in alta parte a binarului (24, respectiv 68 aparitii) — apartin mecanismului intern de motor ("engine turn 1 start/end", vazut si in log-ul de debug la rularea reala), nu suprafetei de hooks expuse pluginurilor.prompt.submite confirmat, si separat, la offset 206959908, exista codul intern real care implementeaza acest hook point: un pipelinecore/managedcu posibilitate de a rescrie textul promptului ("prompt.submit: text rewritten by a hook (...)") sau de a-l bloca ("Prompt dropped by a hook: ..."). Semnatura exacta gasita e apelul.prompt.submit({submission, origin, turnId, shouldWait})— nu am gasit litera$ca alias/receiver exact in siruri; forma$.prompt.submit({text})din blog e plauzibila ca sugar-syntax peste acest mecanism, dar nu e confirmata verbatim.- API-uri de tip "calls" confirmate ca namespace-uri cu metode:
fs.read/list/stat(deci$.fsprobabil e un namespace, nu o valoare simpla),store.get/set,clock.after/every/now,session.id/messages— coincide cu ce cerea sarcina sa verific ($.session.messages(),$.fs,$.store,$.clock.every), dar iar, prefixul$.in sine nu apare ca sir literal langa aceste nume — e o inferenta rezonabila, nu o citare directa.
2d. Tokeni / context / usage / compactare — CAUTATE EXPLICIT, NU GASITE
Am cautat explicit token, usage, context, compact in vecinatatea manifestului de mai
sus si in restul zonei de "function hooks". Niciunul dintre cele 20 de calls sau 8 hooks
de mai sus nu are legatura cu tokeni, context window, usage sau compactare. Sirurile
contextWindow (8 aparitii) si compact (405 aparitii) exista din abundenta in binar, dar in
alte module (afisarea usage-ului in --output-format json, autocompact intern al motorului
— vazut si in logul de debug: autocompact: tokens=[REDACTED] level=ok effectiveWindow=980000),
nu ca hook sau call disponibil unui modul de function hooks. Nu exista dovada in acest
binar ca un modul de hooks ar putea citi tokenii ramasi sau starea de compactare.
2e. Forma register si hooks/hooks.json
Doua descoperiri, ambele reale, dar doua lucruri diferite:
-
hooks.jsondin binar (27 aparitii) e manifestul VECHI/existent de plugin-hooks (PreToolUse, SessionStart etc. — cel folosit deja de pluginulponytaildin acest proiect, vazut in logul de debug:Read manifest hooks for plugin ponytail (enabled=true): ./hooks/claude-codex-hooks.json). Contextul din binar confirma: sirul e"plugin hooks.json", langa mesaje despreSKILL.mdsi actualizarea regulilor de auto-mode/permisiuni — sistemul clasic de hooks pe evenimente de tool-use, nesuprapus cu "function hooks". -
Forma
registerpentru "function hooks" (modulul nou), gasita in doua exemple interne reale de pluginuri deja construite cu acest mecanism:tengu_quiet_dolphin— "panoul de diff ca plugin: /diff", cuisAvailable:()=>AtundeAt=()=>h8()&&lu(Rr(),bt)— confirma ca h8() (poarta de function hooks descrisa la 2a) chiar gateaza un plugin real deja construit, nu doar un flag mort.tengu_tips_mod— "spinner tips as a plugin", cu forma exacta:adica modulul exporta unvar P=(e)=>({register:(o)=>{e.registerHooks(o,x())}});register(engine)care apeleazaengine.registerHooks(implementareHooks, apiSuplimentar). E cea mai apropiata dovada gasita de "forma exacta a functiei register", dar e exemplul unui plugin intern concret, nu semnatura generica documentata explicit undeva ca atare.
VERDICT
Se poate construi o bucla "prag context -> scrie handoff -> /clear -> reia din handoff" complet automat, FARA interventia utilizatorului, folosind acest surface?
Nu, nu cu ce am vazut efectiv in acest binar. Motive, punct cu punct:
- Suprafata de hooks confirmata (
session.start,ui.*,command.run,tool.call,prompt.submit) nu contine niciun eveniment sau apel legat de prag de context, tokeni, usage sau compactare (sectiunea 2d) — deci un modul de hooks n-ar avea de unde sa afle "am trecut de 200k tokeni" din interior. Ar trebui sa deduca asta indirect (ex. numarand caractere dinsession.messages()), fara nicio garantie ca se potriveste cu tokenizarea reala sau cuautocompactintern. - Nu exista niciun hook de tipul "inainte de compactare" / "sesiune se inchide" in lista — deci nu exista un punct de agatare curat pentru "scrie handoff chiar inainte sa se piarda contextul".
/clear(sau relansarea sesiunii) nu apare incalls-urile confirmate (command.registerexista, dar a inregistra o comanda noua nu e totuna cu a declansa/clearprogramatic din interiorul unui hook).- Flag-ul e in continuare oprit implicit (
tengu_plugin_hooks_modulesdefault!1), marcat "early access" chiar in textul din comanda/plugin-typesinsasi ("module 'claude-code', early access: it may change between releases") — deci si daca API-ul de mai sus ar acoperi cazul, Anthropic il trateaza explicit ca nestabil. - Nu am reusit sa validam empiric nimic din surface (n-am putut genera
.d.tsdin cauza limitarii de mediu descrise la sectiunea 1) — tot ce e mai sus e din citirea codului minificat, nu din declaratii TypeScript generate si citite ca atare, cum cerea procedura.
Ce ar lipsi/ar trebui reincercat ca sa se stie sigur: rulare interactiva reala (TTY, nu
-p/SDK) cu CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1, ca sa se vada daca /plugin-types chiar
scrie fisierele si daca h8() se evalueaza true in acel context — asta ar da acces la
declaratiile TypeScript reale si ar inchide toate necunoscutele de mai sus (in special conditiile
qb()/Br("hooks")/zm() neatribuite cu certitudine).