From b516a800bf6a4a6e8c3f2681d03e60986898a72c Mon Sep 17 00:00:00 2001 From: Erik Simon Date: Sun, 23 Aug 2026 08:55:17 +0200 Subject: [PATCH] filter: make drops visible in the gateway log Every drop so far has been silent. The script printed its reason to stderr and exited 0 with "[SILENT]", but Hermes only logs stderr on the nonzero path, as script ignored webhook path=... code=... stderr=... so from outside, a deliberate drop, a crash, a timeout and a missing file all looked identical: {"status":"ignored","reason":"script"} and nothing else. Finding out which one it was meant re-running the payload through the script by hand. Drops now exit 3 with an empty stdout. Both still mean "ignored" to Hermes, but the reason lands in the log. Exit 3 rather than 1 keeps a deliberate drop distinguishable from an unhandled exception, which exits 1, so the code alone says which happened. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01S94o42aQ8VkBmEWvDem5xa --- hosts/mars/gitea-pr-comment-filter-test.py | 19 +++++++++++++----- hosts/mars/gitea-pr-comment-filter.py | 23 +++++++++++++++++++--- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/hosts/mars/gitea-pr-comment-filter-test.py b/hosts/mars/gitea-pr-comment-filter-test.py index a2ead1d..a157d30 100644 --- a/hosts/mars/gitea-pr-comment-filter-test.py +++ b/hosts/mars/gitea-pr-comment-filter-test.py @@ -89,12 +89,21 @@ for path in [("comment","id"), ("comment","body"), ("comment","user","login"), print(f"{'PASS' if ok else 'FAIL'} {'prompt path survives: {' + label + '}':<52} {cur if ok else 'MISSING'}") if not ok: fails.append(f"path-{label}") -# --- stdout discipline: an ignore must emit EXACTLY [SILENT] --- +# --- drop contract: nonzero exit + empty stdout + reason on stderr --- +# Nonzero is what gets the reason into the gateway log (Hermes logs +# "script ignored webhook path=... code=... stderr=..." only on that path). rc, out, err = run(payload(author="luna")) -print(f"{'PASS' if out == chr(91)+'SILENT'+chr(93)+chr(10) else 'FAIL'} {'ignore emits exactly [SILENT] on stdout':<52} {out!r}") -if out != "[SILENT]\n": fails.append("silent-exact") -print(f"{'PASS' if err.strip() else 'FAIL'} {'ignore explains itself on stderr':<52} {err.strip()[:40]!r}") -if not err.strip(): fails.append("stderr-reason") +print(f"{'PASS' if rc == 3 else 'FAIL'} {'drop exits 3 (not 0, so Hermes logs it)':<52} rc={rc}") +if rc != 3: fails.append("drop-exit-code") +print(f"{'PASS' if out == '' else 'FAIL'} {'drop writes nothing to stdout':<52} {out!r}") +if out != "": fails.append("drop-stdout-empty") +print(f"{'PASS' if 'luna' in err else 'FAIL'} {'drop names the rule on stderr':<52} {err.strip()[-44:]!r}") +if "luna" not in err: fails.append("stderr-reason") + +# a crash must stay distinguishable from a deliberate drop +rc, out, err = run("not-a-dict") +print(f"{'PASS' if rc == 3 else 'FAIL'} {'malformed payload is a drop (3), not a crash':<52} rc={rc}") +if rc != 3: fails.append("malformed-exit-code") print() print("ALL PASSED" if not fails else "FAILURES: " + ", ".join(fails)) diff --git a/hosts/mars/gitea-pr-comment-filter.py b/hosts/mars/gitea-pr-comment-filter.py index cd95313..2556f28 100644 --- a/hosts/mars/gitea-pr-comment-filter.py +++ b/hosts/mars/gitea-pr-comment-filter.py @@ -12,7 +12,19 @@ STDOUT IS A PROTOCOL CHANNEL, not a log: That last case is why every diagnostic here goes to stderr. A stray print() would not drop an event, it would let one through. -Empty stdout, a nonzero exit, a missing script, or a timeout also count as +Drops exit with DROP_EXIT_CODE and an empty stdout rather than printing +"[SILENT]" and exiting 0. Both mean "ignored" to Hermes, but only the nonzero +path is logged, as + + script ignored webhook path=... code=3 stderr=... + +which puts the reason in the gateway log. On the exit-0 path the reason goes +to stderr and is never surfaced anywhere, so a drop is indistinguishable from +a crash from a missing file -- which cost a long debugging detour once +already. code=3 is what separates a deliberate drop from a real crash: a +traceback exits 1. + +Empty stdout, a nonzero exit, a missing script, or a timeout all count as "ignored", so this script fails CLOSED: if it breaks, nothing reaches the agent rather than everything. That is the right direction for a loop guard, but it does mean a syntax error silently disables the whole integration -- @@ -35,6 +47,11 @@ import sys # and you do not want her reacting to build output. IGNORED_AUTHORS = {"luna"} +# Exit code for a deliberate drop. Anything nonzero makes Hermes ignore the +# delivery AND log the reason; 3 distinguishes "a rule fired" from an +# unhandled exception, which exits 1. +DROP_EXIT_CODE = 3 + # Gitea's HookIssueCommentAction values are created / edited / deleted. # "deleted" is dropped: the payload still carries the comment body, so letting # it through would have her act on a request that was explicitly withdrawn. @@ -42,9 +59,9 @@ ALLOWED_ACTIONS = {"created", "edited"} def ignore(reason: str) -> None: + """Drop the delivery, loudly enough to find in the gateway log.""" print(f"gitea-pr-comment-filter: ignoring delivery: {reason}", file=sys.stderr) - print("[SILENT]") - raise SystemExit(0) + raise SystemExit(DROP_EXIT_CODE) def main() -> None: