Skip to content
Agentmart
Submit a tool

kopf-conventions

kopf mechanics for Python Kubernetes operators.

Skill by leroyguillaume

IndexedMIT★ 7
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/leroyguillaume-kopf-conventions

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

kopf mechanics for Python Kubernetes operators. TRIGGER when: code imports `kopf` or defines `@kopf.on.*`/`@kopf.timer` handlers; wiring `OperatorSettings`; emitting events from a Python operator; user asks why events don't show up, or about `kopf.event`, `kopf.TemporaryError` or `on.resume`. SKIP when: the operator is not kopf, or the question is framework-agnostic reconcile design (`kubernetes-operator-conventions`).

Use cases: design, devops

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/kopf-conventions

What's inside

Skill name
kopf-conventions
Description
kopf mechanics for Python Kubernetes operators. TRIGGER when: code imports `kopf` or defines `@kopf.on.*`/`@kopf.timer` handlers; wiring `OperatorSettings`; emitting events from a Python operator; user asks why events don't show up, or about `kopf.event`, `kopf.TemporaryError` or `on.resume`. SKIP when: the operator is not kopf, or the question is framework-agnostic reconcile design (`kubernetes-operator-conventions`).
License
MIT
Instructions
227 lines in SKILL.md · view on GitHub

Files (1)

  • SKILL.md

SKILL.md (first lines)

# kopf conventions

kopf-specific implementation details for Python operators. The
framework-agnostic reconcile principles (always-requeue, idempotency,
finalizers, ownerReferences, field manager) live in
`kubernetes-operator-conventions` — this skill is the kopf wiring that those
principles compile down to, plus the sharp edges that bite in practice.

## Events & posting — the model you must get right

kopf has **two completely separate** ways an event reaches the cluster, gated
by **two different settings**. Confusing them is the #1 cause of "I had errors
but no events showed up on the resource".

1. **Explicit calls** — `kopf.event(body, type=..., reason=..., message=...)`
   posts **iff `settings.posting.enabled` is True** (the default), whatever
   `posting.level`. The shorthands also check the level: `kopf.info` needs
   `level <= INFO`, `kopf.exception` `level <= ERROR`, and `kopf.warn`
   `level <= WARNING` while ignoring `enabled`. Full control over
   `type` / `reason` / `message`.
2. **Log-record posting** — kopf can mirror log lines made on the **object
   logger** (`logger` injected into a handler) as events. This is gated by
   **`settings.posting.loggers`**, which **defaults to `False`**. When off,
   `logger.error(...)` / `logger.warning(...)` are logged but **never posted**.
   `posting.enabled` does **not** turn this on — only `posting.loggers` does.

### The trap

> **`raise kopf.TemporaryError(...)` does NOT, on its own, post a Kubernetes
> event.** The error event you expect only exists if either (a) you post it
> explicitly with `kopf.event`, or (b) `settings.posting.loggers = True` so the
> framework's error log of the failure becomes an event.

Setting only `settings.posting.enabled = True` (a very common operator setup)
buys you nothing for errors raised via `TemporaryError`, because that path
surfaces through the **logger**, not an explicit `kopf.event`. Any docstring or
skill that says "kopf posts a Warning event for free when you raise
TemporaryError, provided posting.enabled is on" is **wrong** — it needs
`posting.loggers`.

kopf's log-to-event handler posts a record only when **all** of these hold: `posting.enabled`, `record.levelno >= posting.level`,
**`posting.loggers`**, and the record carrying a `k8s_ref` (i.e. it was logged
on the *object* logger, not a module-level `logging.getLogger(...)`).

### Surfacing errors as events — pick one, deliberately

- **Explicit lifecycle events (preferred for quality).** Keep `posting.loggers`
  off and post your own events at the reconcile level — good `reason`s, one
  event per outcome, no framework duplication. Wrap the handler:

  ```python
  @kopf.on.create(GROUP, VERSION, PLURAL)
  @kopf.on.resume(GROUP, VERSION, PLURAL)
  @kopf.on.update(GROUP, VERSION, PLURAL)
  async def reconcile(spec, body, name, logger, **_):
      try:
          await _reconcile(spec, body, name, logger)
      except Exception as exc:
          kopf.event(body, type="Warning", reason="ReconcileFailed",
                     message=f"Reconcile failed: {exc}")
          raise
      kopf.event(body, type="Normal", reason="Reconciled",
                 message="Desired state applied.")
  ```

  Make the raised `TemporaryError` message carry the failing operation + cause
  (e.g. `"creating the backing database failed: 500 ..."`) so the single
  rolled-up `ReconcileFailed` event still names what broke. Re-raising
  preserves the requeue.

- **Granular log-posted events (set both knobs).** If you want one event per
  failing operation straight from the shared error chokepoint without plumbing
  `body` everywhere, set in the startup handler:

  ```python
  settings.posting.enabled = True
  settings.posting.loggers = True
  settings.posting.level   = logging.WARNING   # NOT the INFO default
  ```

  Without raising the level off its `logging.INFO` default, **every** info line
  becomes an event and floods the object. That level also silences
  `kopf.info`, so post Normal events with `kopf.event(type="Normal")`, as the
  wrapper above does. Even then the posted events carry
  `reason="Logging"` (less descriptive than an explicit one), and the framework
  may also post its own error log — expect duplicates.

Pick explicit events for control; pick `posting.loggers` only when you
genuinely want per-line mirroring. Don't half-set it (`enabled` without
`loggers`) and assume errors post — they won't.

### Cluster-scoped objects & other event gotchas

- **Events are namespaced; cluster-scoped CRs are not.** For a cluster-scoped
  object kopf posts the event into the operator's *own* namespace
  (`get_default_namespace()`, falling back to `default`), with `involvedObject`
  pointing at the CR. `kubectl describe <clusterscoped>` still finds it by uid.
- **RBAC.** The operator's (Cluster)Role needs this rule; missing it is
  silent (see next point):

  ```yaml
  - apiGroups:
      - ""
    resources:
      - events
    verbs:
      - create
      - patch
  ```
- **POST failures are swallowed.** `events.post_event` catches API errors and
  only emits `logger.warning("Failed to post an event. ... Code: 403 ...")`.
  Events never fail the handling cycle — so when events are mysteriously
  absent, **grep the operator logs for "Failed to post an event"** before
  suspecting your code.
- **Messages are truncated** to 1024 chars by kopf; keep them short.

## RBAC — the operator needs more than its own CRs

The (Cluster)Role must grant, beyond the obvious verbs on your own custom

From leroyguillaume/claude, MIT.

More from leroyguillaume/claude

Signals

Stars
7
Forks
3
Open issues
0
Last commit (this folder)
Oct 4, 2026 (24 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from leroyguillaume

Cite this page

kopf-conventions skill. Agent Mart. https://agentmart.dev/skills/leroyguillaume-kopf-conventions (last updated 2026-10-24)