Skip to content

feat(triggers): runtime mute config for SKILL_OBSERVATION_TRIGGER#40

Merged
AVADSA25 merged 1 commit intomainfrom
claude/nice-ishizaka-3560a2
May 3, 2026
Merged

feat(triggers): runtime mute config for SKILL_OBSERVATION_TRIGGER#40
AVADSA25 merged 1 commit intomainfrom
claude/nice-ishizaka-3560a2

Conversation

@AVADSA25
Copy link
Copy Markdown
Owner

@AVADSA25 AVADSA25 commented May 3, 2026

Summary

  • Adds ~/.codec/triggers.json runtime config so users can soft-disable a trigger by skill name without editing the skill file. Two knobs: muted_skills (permanent) and muted_until (ISO-8601 timestamp).
  • New trigger_muted audit event (warning level) — muted-but-otherwise-eligible matches stay visible in the log, unlike the silent triggers_killed.json kill switch.
  • Restores 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_TRIGGER block. That works but:

  • requires editing the skill file,
  • can't be re-enabled per user,
  • loses the declaration entirely.

The existing per-(skill, pattern_hash) kill switch in triggers_killed.json is silent (no audit emit), and TRIGGERS_ENABLED=false is 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 trigger trigger_blocked: cooldown emits, and do not invoke the consent gate. They emit a single trigger_muted event with mute_source ∈ {muted_skills, muted_until}.

Tests

3 new in tests/test_triggers.py §7.6:

  • test_muted_skill_name_skips_fire — full integration through evaluate(), asserts no dispatch + trigger_muted audit emit
  • test_muted_until_past_timestamp_not_muted — expired timestamps don't mute
  • test_muted_until_future_timestamp_muted — future timestamps mute

Result: 38/38 trigger tests pass (35 baseline + 3 new). All other test failures are pre-existing on main (pynput import, see docs/known-issues.md).

Test plan

  • pytest tests/test_triggers.py — 38/38 pass
  • pytest tests/test_clipboard_url_fetch.py — 14/14 pass (1 pre-existing pynput-import failure)
  • pytest tests/test_observer.py — passes
  • Confirmed test_destructive_consent::test_is_destructive_tool_triggers_on_http_blocked failure is pre-existing on baseline (verified via git stash)
  • Reviewer to spot-check: drop a fresh ~/.codec/triggers.json with {"muted_skills": []}, restart codec-observer, copy a URL → confirm clipboard_url_fetch now auto-fires (verifies file overrides defaults)
  • Reviewer to spot-check: tail ~/.codec/audit.log while triggering a clipboard pattern → confirm one trigger_muted warning emitted with mute_source: "muted_skills"

🤖 Generated with Claude Code

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>
@AVADSA25 AVADSA25 merged commit 5c8dc36 into main May 3, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants