feat(triggers): runtime mute config for SKILL_OBSERVATION_TRIGGER#40
Merged
feat(triggers): runtime mute config for SKILL_OBSERVATION_TRIGGER#40
Conversation
Adds `~/.codec/triggers.json` so users can soft-disable a trigger by skill name (permanently or via `muted_until` ISO-8601 timestamp) without editing the skill file. Default contents preserve PR #38's behavior (`clipboard_url_fetch` muted) when the file is absent. PR #38 muted the noisy clipboard URL trigger by commenting out its `SKILL_OBSERVATION_TRIGGER` block. That works but loses the declaration and can't be re-enabled per user. The existing `triggers_killed.json` kill switch is per-(skill, pattern_hash) and silent; the global `TRIGGERS_ENABLED` env var is a sledgehammer. Mute fills the gap with a per-skill, audit-visible (`trigger_muted`, warning) suppression layer. Wired in `evaluate()` between `_emit_evaluated` and the cooldown check, so muted matches don't burn cooldown budget and stay visible in audit. Restored `clipboard_url_fetch.SKILL_OBSERVATION_TRIGGER` (uncommented); old behavior preserved by config defaults, not commented-out code. Tests: 3 new in tests/test_triggers.py §7.6 (38/38 trigger tests pass). Docs: docs/PHASE2-STEP6-TRIGGER-MUTE.md (config shape, examples, re-enable, kill-vs-mute comparison). AGENTS.md §3 + §6 + §10 updated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
~/.codec/triggers.jsonruntime config so users can soft-disable a trigger by skill name without editing the skill file. Two knobs:muted_skills(permanent) andmuted_until(ISO-8601 timestamp).trigger_mutedaudit event (warning level) — muted-but-otherwise-eligible matches stay visible in the log, unlike the silenttriggers_killed.jsonkill switch.clipboard_url_fetch.SKILL_OBSERVATION_TRIGGER(uncommented). Old PR hotfix: stop trigger + shift_report notification spam #38 behavior is preserved by config defaults ({"muted_skills": ["clipboard_url_fetch"]}when the file is absent), not by commented-out code.Why
PR #38 muted the noisy clipboard URL trigger by commenting out its
SKILL_OBSERVATION_TRIGGERblock. That works but:The existing per-(skill, pattern_hash) kill switch in
triggers_killed.jsonis silent (no audit emit), andTRIGGERS_ENABLED=falseis a global sledgehammer. This PR adds a per-skill, audit-visible suppression layer — see docs/PHASE2-STEP6-TRIGGER-MUTE.md §6 for the kill-vs-mute comparison table.Wiring
In
evaluate(), mute is checked between_emit_evaluated()and the cooldown check. Muted matches do not consume cooldown budget, do not triggertrigger_blocked: cooldownemits, and do not invoke the consent gate. They emit a singletrigger_mutedevent withmute_source ∈ {muted_skills, muted_until}.Tests
3 new in
tests/test_triggers.py§7.6:test_muted_skill_name_skips_fire— full integration throughevaluate(), asserts no dispatch +trigger_mutedaudit emittest_muted_until_past_timestamp_not_muted— expired timestamps don't mutetest_muted_until_future_timestamp_muted— future timestamps muteResult: 38/38 trigger tests pass (35 baseline + 3 new). All other test failures are pre-existing on
main(pynput import, seedocs/known-issues.md).Test plan
pytest tests/test_triggers.py— 38/38 passpytest tests/test_clipboard_url_fetch.py— 14/14 pass (1 pre-existing pynput-import failure)pytest tests/test_observer.py— passestest_destructive_consent::test_is_destructive_tool_triggers_on_http_blockedfailure is pre-existing on baseline (verified viagit stash)~/.codec/triggers.jsonwith{"muted_skills": []}, restartcodec-observer, copy a URL → confirmclipboard_url_fetchnow auto-fires (verifies file overrides defaults)~/.codec/audit.logwhile triggering a clipboard pattern → confirm onetrigger_mutedwarning emitted withmute_source: "muted_skills"🤖 Generated with Claude Code