Runtime Patch Layer

Need to fix or tweak the behavior of a built-in OpenClacky method without forking the whole gem? The runtime patch layer lets you declare a prepend patch targeting a specific method. Its headline feature is optional fingerprint verification: once you provide a fingerprint, if the upstream method's source changes on a gem upgrade, your patch is automatically disabled — instead of hard-applying to new code and silently turning into a harder-to-spot bug.

Patches are now one kind of extension-container contribution — declare contributes.patches in ext.yml and ship the patch script inside ~/.clacky/ext/local/<id>/. To understand the overall model first, read the Extension System Overview. This mechanism only supports method-level overrides (prepend a new method body) and deliberately does not support line-level patch / diff.


How it works

An extension container declares the patches it contributes in ext.yml:

# ~/.clacky/ext/local/fix-search/ext.yml
id: fix-search
name: Search Timeout Fix
version: "0.1.0"
contributes:
  patches:
    - target: "Clacky::Tools::WebSearch#execute"  # method being prepended
      file: patches/timeout.rb                    # patch file (relative to container root)
      fingerprint: "a1b2c3..."                    # optional: source fingerprint of the target
      on_mismatch: disable                        # on mismatch: disable (default) | warn

At process start, OpenClacky resolves every container and, for each patch contribution:

  1. fingerprint omitted → trust the author, require the patch file directly, the prepend takes effect.
  2. fingerprint givenrecompute the target method's current source fingerprint and compare with the recorded value:
    • Match → apply the patch.
    • Mismatch → handle per on_mismatch (default disable: not applied + warning).

That means: if you added a fingerprint, patches that may no longer apply after a gem upgrade are auto-quarantined — never "hard-applied" into a silent failure. A single failing patch quarantines only itself and doesn't affect the others.

What a Patch File Looks Like

A patch file is just a prepend module that's prepended onto the target class; you can call the original implementation with super:

# ~/.clacky/ext/local/fix-search/patches/timeout.rb

module FixSearch
  def execute(query:)
    @timeout = 30      # e.g. bump timeout to 30s
    super              # then call the original
  end
end

Clacky::Tools::WebSearch.prepend(FixSearch)

target syntax:

  • Const::Path#method — instance method
  • Const::Path.method — class method
  • No line numbers, file paths, or regex forms.

The on_mismatch Option

Only meaningful when fingerprint is provided:

Value Behavior
disable (default) Not applied, prints a warning.
warn Not applied, prints a warning (different wording, similar semantics — useful to distinguish during debugging).

There is no "force apply" option. This is deliberate — hard-applying a mismatched patch is exactly what this mechanism guards against. If you don't want drift protection, just omit fingerprint (trust mode).

CLI Workflow

1. Scaffold

clacky ext new fix-search --full

The --full reference container includes a patches/audit.rb sample (plus the other six contributions). You can also run clacky ext new fix-search, trim ext.yml down to just contributes.patches, and write the patch file yourself.

2. Write the patch

Follow the sample above. Don't delete the trailing .prepend(...) line — that's the action that actually makes the patch take effect.

3. Verify status

clacky ext verify

Sample output:

[OK]   fix-search/timeout (patch, local)
[WARN] fix-search/timeout (patch.fingerprint.mismatch) — upstream code for Clacky::Tools::WebSearch#execute changed
  • [OK]: loaded and prepend is in effect.
  • [WARN]: fingerprint mismatch, handled per on_mismatch (default not applied); you need to look at the new method and decide whether to rewrite the patch.

Workflow After an Upgrade

After a gem upgrade, if your patch carries a fingerprint, strongly consider running clacky ext verify immediately:

  1. See which patches report a fingerprint mismatch.
  2. Compare against the new method source and update the patch logic.
  3. Recompute and fill in the new fingerprint (or temporarily omit fingerprint to run in trust mode).

When Not to Use a Patch

  • Adding a new method / new class: there's no "drift" risk here, so skip the patch mechanism — write a skill or monkey-patch inside your own project.
  • Changing a constant / config default: use ~/.clacky/config.yml or an env var, don't touch code.
  • A large cross-method overhaul: this most likely warrants forking the gem or opening a PR, not stacking multiple patches — the more patches, the higher the chance they all break on upgrade.

The patch mechanism fits best when: one very specific method has one very localized fix.

⚠️ Patch files are arbitrary Ruby that mutate runtime behavior directly — the highest supply-chain risk of all. Before installing someone's extension, check which patches it contributes and which methods they change.