Plugin-Troubleshooting
Manifest-, Codegen-, Host- und Laufzeitfehler — was jeder einzelne bedeutet und wie Sie ihn beheben.
Plugin-Fehler sind bewusst laut und brechen fail-closed ab. Nahezu jeder fällt in eine von vier Familien: Das Manifest ist ungültig, die Komposition ist ungültig oder veraltet, die Autorisierung wurde verweigert, oder eine Contribution ist in ihren deklarierten sicheren Fallback gelaufen.
Manifest-Fehler
definePlugin und parsePluginManifest werfen PluginManifestError mit einem Issue je Problem: { code, path, message }, wobei path ein JSON-Pointer-ähnlicher String wie $.contributes.crons[0].timeoutMs ist. validatePluginManifest liefert dieselben Issues zurück, ohne zu werfen.
| Issue-Code | Typische Ursache |
|---|---|
missing | Ein Pflichtfeld fehlt — meist $.flag bei einem Plugin, das Contributions liefert, Storage nutzt oder llm:invoke anfordert; oder in $.capabilities fehlt die Capability, die ein deklarierter Bucket voraussetzt |
invalid_type | Falscher JavaScript-Typ oder eine Accessor-Property dort, wo Daten verlangt waren |
invalid_format | id ist nicht kleingeschriebenes Kebab-Case mit ≤ 64 Zeichen, version ist kein Semver, eine Capability ist nicht domain:action, eine Umgebungsvariable ist nicht UPPER_SNAKE, oder ein Export-Pfad ist kein sicherer relativer Package-Export |
duplicate | Eine wiederholte Capability oder eine wiederholte erforderliche Umgebungsvariable |
unknown_field | Ein Feld oder Contribution-Bucket, den die Plattform nicht kennt |
too_many_items | Über einer Obergrenze: 64 Capabilities, 64 Umgebungsvariablen, 256 Einträge pro Bucket, 64 Settings-Felder |
accessor_not_allowed | Ein Getter/Setter dort, wo der Validator reine Daten verlangt — Manifeste werden als Daten inspiziert und niemals aufgerufen |
Häufige Anfängerfehler:
- "must declare
<capability>when<nouns>are contributed" — fügen Sie die Capability-Konstante zucapabilitieshinzu. - "
$.flagmust be a plain object when …" — jeder Contribution-Bucket, beide Storage-Capabilities undllm:invokeerfordern ein explizitesflag. llmBudgetabgelehnt —dailyUsdmuss > 0 und ≤ 1 000 000 sein und mit höchstens sechs Nachkommastellen darstellbar sein.- Cron abgelehnt —
intervalMinutesmuss zwischen 15 und 40 320 liegen undtimeoutMszwischen 1 000 und 300 000. - "must be listed in
$.flag.requiredEnvVars" — dassignature.secretEnvVareines Feedback-Webhooks muss zusätzlich inflag.requiredEnvVarsauftauchen. Ohne das Secret kann die Route nur mit503antworten, und durch das Auflisten wird das Plugin so lange nicht aktivierbar, bis ein Operator es setzt.
Codegen-Fehler
PluginCodegenError trägt einen Code plus eine Detailliste.
| Code | Bedeutung und Behebung |
|---|---|
config_invalid | plugins.config.ts ist keine literale Liste von Package-Namen. Sie wird als Daten geparst, niemals ausgewertet — belassen Sie sie als einfaches Array |
dependency_missing | "Bundled plugin <name> must be installed as a root dependency or optionalDependency." Installieren Sie es im Workspace-Root aus der Registry und führen Sie dann add erneut aus |
dependency_provenance | Installierte Metadaten, die Integrität von bun.lock oder die Realpath-Eingrenzung haben nicht übereingestimmt. Aliase sowie git-/URL-/file-/workspace-Quellen scheitern per Design fail-closed |
package_load_failed | Der Manifest-Eintrag des Packages konnte nicht importiert werden. Prüfen Sie, dass der Default-Export das definePlugin-Manifest ist und dass sein Import keine Seiteneffekte hat |
conditional_manifest_export | Der Manifest-Export ist bedingungsabhängig, sodass Bun, Convex, Nuxt-SSR und der Browser-Build unterschiedliche Manifeste auflösen könnten. Verwenden Sie einen einzigen bedingungsunabhängigen Root-Export |
invalid_manifest | Das geladene Manifest hat die Validierung nicht bestanden — siehe die Manifest-Tabelle oben |
component_export_invalid / contribution_export_invalid | Ein deklarierter exportPath ist kein exakter, existierender, bedingungsunabhängiger Package-Export |
composition_invalid | Doppelte Package-Namen, doppelte Manifest-Ids oder ein Contribution-Graph, den der Host ablehnt (unbekannter oder terminaler Agent-Step-Anker, doppelte Art, Einfügezyklus, unsichere Lifecycle-Kante) |
generated_files_stale | --check hat fehlende oder veraltete Ausgabe gefunden. Führen Sie bun run plugins:codegen aus und committen Sie das Ergebnis zusammen mit der Konfigurationsänderung |
generated_path_unsafe | Ein generiertes Ziel oder ein übergeordnetes Verzeichnis ist ein Symlink. Die Ausgabe wird über eine exklusive temporäre Datei und ein atomares Rename geschrieben und folgt Links grundsätzlich nicht |
direct_plugin_import | Core-Quellcode importiert ein konfiguriertes Plugin-Package außerhalb der generierten Kompositionsdateien — auch über einen Node-/Bun-Loader oder einen Repository-Alias. Leiten Sie den Import über die generierte Registry |
repository_inventory_invalid | Der Boundary-Scan konnte sein begrenztes Datei-Inventar nicht aufbauen, oder das Repository überschreitet das Datei-Sicherheitslimit des Scans |
repository_config_invalid | Eine Repository-Alias-Konfiguration (tsconfig-Paths, Bundler-Aliase) konnte nicht sicher gelesen werden, oder die Alias-Erkennung hat ihr Dateilimit überschritten |
source_invalid | Eine Quelldatei konnte beim Scannen für die Import-Boundary-Regel nicht geparst werden |
workspace_not_found | Der Befehl wurde außerhalb des Owlat-Workspace ausgeführt |
Cannot find module '@owlat/plugin-kit' beim Aufruf der CLI oder der Codegen aus dem Quellcode bedeutet, dass die Contracts noch nicht gebaut wurden. Führen Sie einmal bun run plugins:prepare aus (die Skripte plugins:codegen / plugins:check erledigen das für Sie).
Dasselbe gilt, wenn Sie die Tests von packages/plugin-codegen oder packages/plugin-cli direkt mit vitest ausführen. Diese Packages bauen die Contracts bewusst nicht selbst neu: packages/plugin-kit/dist ist ein gemeinsam genutztes Artefakt, das mit tsup --clean neu gebaut wird, sodass ein package-lokaler Rebuild es unter allem anderen wegräumen würde, was der Lauf gerade kompiliert. Stattdessen besitzt Turbo diese Reihenfolge (test hängt von @owlat/plugin-kit#build ab), und bun run lint:build-graph schlägt fehl, wenn ein Package-Skript wieder anfängt, es neu zu bauen.
Dieser Schutz deckt auch packages/plugin-kit selbst ab — nur dessen build sowie die ausschließlich fürs Publishing gedachten Hooks prepack / postpack dürfen tsup --clean antreiben, weshalb der Package-Smoke-Test mit --ignore-scripts packt und prüft, dass dist/ unberührt geblieben ist — sowie das Repository-Root, wo plugins:prepare der eine sanktionierte Einstiegspunkt ist: Jedes andere Root-Skript, das mit --cwd in ein anderes Workspace-Verzeichnis greift, lässt den Schutz fehlschlagen, sofern es nicht in ROOT_CWD_ALLOWLIST in scripts/check-build-graph.ts aufgenommen wird.
Host-Ablehnungen
PluginHostError wird an der Enforcement-Grenze ausgelöst. Sein Code sagt genau, welches Glied der Kette gerissen ist.
| Code | Bedeutung | Behebung |
|---|---|---|
capability_not_declared | Das Manifest hat diese Capability nie angefordert | Fügen Sie sie dem Manifest hinzu und deployen Sie neu — eine Gewährung kann ein Manifest nicht erweitern |
capability_not_granted | Deklariert, aber der Operator hat sie nicht gewährt | Aktivieren Sie das Plugin und genehmigen Sie seine Capabilities unter Einstellungen → Plugins |
plugin_disabled | Das Feature-Flag ist aus | Aktivieren Sie das Flag |
required_environment_missing | Eine Variable aus flag.requiredEnvVars fehlt | Setzen Sie sie in der Deployment-Umgebung; die Aktivierung ist blockiert, bis jede einzelne vorhanden ist |
environment_check_failed / feature_check_failed | Der Host konnte die Umgebung oder das Flag nicht auswerten | Infrastrukturproblem — die Operation bricht fail-closed ab, was so gewollt ist |
invalid_capability_grant / invalid_manifest_snapshot / invalid_contribution | Host-Konfiguration oder Komposition sind inkonsistent | Führen Sie die Codegen erneut aus; bleibt es bestehen, sind Komposition und Deployment auseinandergedriftet |
untrusted_output_rejected | Ein Plugin hat an einer Textgrenze etwas anderes als einen String zurückgegeben, oder der Scrubber ist fehlgeschlagen | Geben Sie aus dem Modul einen einfachen String zurück |
invalid_untrusted_text_policy | Ein Host-Adapter hat eine ungültige Policy benannt | Host-Bug, kein Plugin-Bug |
"Meine Contribution läuft nicht"
Arbeiten Sie diese Liste ab — es ist dieselbe Reihenfolge, in der der Host prüft:
- Ist das Package in
plugins.config.tseingetragen und ist die generierte Komposition aktuell (bun run plugins:check)? - Wurde das Deployment nach der Konfigurationsänderung neu gebaut? Die gebündelte Komposition ist ein Build-Zeit-Artefakt.
- Ist das Feature-Flag des Plugins aktiviert?
- Wurden seine Capabilities bei der Aktivierung genehmigt? Das Deaktivieren löscht den Gewährungsdatensatz.
- Sind alle
flag.requiredEnvVarsin der Deployment-Umgebung vorhanden? - Deklariert das Manifest die Capability, die der Bucket voraussetzt?
- Bei Navigations- und Einstellungseinträgen: Existiert der Zielabschnitt und ist er feature-seitig aktiv? Besitzt bereits ein anderer Eintrag denselben
href? Die Deduplizierung läuft über href, der zuerst registrierte gewinnt, und Core registriert zuerst.
"Die Contribution lief, aber nichts hat sich geändert"
Das ist meist ein sicherer Fallback, der seine Arbeit tut. Jeder Bucket hat eine deklarierte Richtung:
| Symptom | Wahrscheinliche Ursache |
|---|---|
| Antworten gehen weiterhin in die menschliche Prüfung | Ein Plugin-Gate hat Einspruch erhoben, ist in ein Timeout gelaufen, hat geworfen, fehlerhafte Ausgabe geliefert oder wurde abgewiesen. Gates brechen fail-closed ab |
| Drafts sehen aus wie die eingebauten | Die Draft-Strategie des Plugins wurde abgewiesen, lief in ein Timeout, schlug fehl oder erzeugte übergroße bzw. injektionsartige Ausgabe — ein Rückfall auf default |
| Ein Agent-Step ist aus der Pipeline verschwunden | Die Autorisierung wurde im letzten Moment verweigert. Der Walker überspringt die Contribution und setzt die unveränderte Core-Fortsetzung fort |
| Ein Automations-Step wiederholt sich ständig | Der Plugin-Step hat failed zurückgegeben oder geworfen. Retries und Circuit Breaker gehören dem Walker |
| Ein Hook-Wert wird ignoriert | Die Antwort hat die strikte Validierung nicht bestanden, kam zu spät an, oder der Circuit ist offen. Prüfen Sie den Fallback-Grund im Hook-Zustell-Log |
Tier-2-Hook-Fehler
Schlagen Sie den festen Fallback-Grund im Hook-Zustell-Log nach:
| Grund | Was zu prüfen ist |
|---|---|
blocked_ssrf / redirect_refused | Der Endpoint löst auf eine private/interne Adresse auf oder er leitet weiter. Beides wird verweigert — veröffentlichen Sie einen öffentlichen https-Endpoint, der direkt antwortet |
timeout | Der Endpoint hat länger als 5 s gebraucht |
signature_missing / signature_mismatch | Ihre Antwort ist unsigniert oder falsch signiert. Signieren Sie den kanonischen String der Response, spiegeln Sie die Request-Nonce zurück und verwenden Sie v1=<hex> |
stale_response | Der signierte Zeitstempel der Antwort liegt außerhalb der Toleranz von 30 s — prüfen Sie den Uhrenversatz |
response_too_large | Die Antwort hat 64 KiB überschritten |
invalid_json / invalid_response | Der Body hat nicht exakt die Form für diese Hook-Art. Zusätzliche Keys, falsche Typen und leere Strings werden abgelehnt |
capability_denied | Der Operator hat dieser App diese Hook-Art nicht gewährt |
circuit_open | Fünf aufeinanderfolgende Fehlschläge haben den Breaker ausgelöst; nach 60 s versucht er einen Probeaufruf |
app_disabled / app_revoked | Operator-Aktion — der Widerruf ist terminal |
secret_unavailable | Das versiegelte Secret konnte nicht geöffnet werden. Meist ein geändertes INSTANCE_SECRET — rotieren Sie das Secret der App |
Tier-3-Job-Fehler
| Symptom | Ursache |
|---|---|
| Es erscheinen keine Plugin-Jobs | Heute erwartet: worker:enqueue ist reserviert und es wird kein Host-Enqueue-Adapter ausgeliefert |
worker_timeout | Der Job hat seine host-geklemmte Wall Clock überschritten; die gesamte Prozessgruppe wurde getötet |
worker_failed nach mehreren Versuchen | Der Job hat sein geklemmtes Versuchsbudget (≤ 5) aufgebraucht und endete als failed |
Jobs gehen von selbst zurück auf queued | Lease Reclaim nach einem Absturz oder Neustart des Workers |
| Ein Job kann kein Credential lesen | Korrekt und beabsichtigt. Die Job-Umgebung ist von jedem Ambient-Credential bereinigt; vermitteln Sie es über den Host |