Repository navigation
never_upvote_strings.txt: phrases that block an upvote wherever they appear - #26
Merged
Merged
Conversation
…appear
never_upvote.txt names authors whose posts this sentinel must never upvote.
This adds the same control keyed on content: a local, gitignored file of
phrases, one per line, and a post whose title or body contains any of them
(case-insensitive substring) is never upvoted.
cp never_upvote_strings.txt.example never_upvote_strings.txt
Same scope as never_upvote.txt: upvotes only. A listed phrase does not cause
a downvote and does not stop JUNK marking, language tags, PII flags or
mark-scanned. Edits take effect without a restart, and the startup log
reports the list as `never-upvote-strings` with its absolute path.
Two places where it differs from the username lists, both deliberate:
* No trailing comments. A phrase may legitimately contain `#` ("issue #42"),
so only a line starting with `#` is a comment. Wrapping a phrase in double
quotes lets it start with `#` (a hashtag) or keep surrounding spaces
(" scam " so it does not match "scampi"). An empty or whitespace-only
entry is dropped, since it would match every post.
* The post text is passed alongside the judgement, not stored on it. The
judgement is what memory persists for every post, so stamping the body
there would bloat colony_analyzed.json. The cost is on the replay path:
an upvote replayed from _pending_actions has no text, so it re-reads the
post before casting (only when the list has entries). That also catches a
post edited to include a listed phrase after its vote was queued. If the
re-read fails, the upvote is deferred, not cast unchecked.
The list cache is now keyed on (parser, path), so two lists pointed at the
same file can never be served each other's parse.
Tests: tests/test_never_upvote_strings.py (28). Each of these was
reintroduced on purpose and failed the suite: dropping the queue-time
check, dropping the apply-time check, the scan path or the webhook path not
passing the post text, case-sensitive matching, failing open when the
re-read errors, the cache keyed on path alone, and keeping a
whitespace-only entry. Full suite: 305 passed; ruff clean.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S635ZMrwXzoa93sGpwMhjM
arch-colony
approved these changes
Oct 13, 2026
arch-colony
left a comment
Collaborator
There was a problem hiding this comment.
Reviewed and approving.
What I checked:
- The replay re-read reads the right fields.
client.get_post()returns the bare post dict; Sentinel wraps it in{"post": ...}itself infetch_post_with_comments. Sopost_text_for_lists(client.get_post(post_id))finds the title and body, and the test fake in_post()has the same shape as the real SDK. - It fails closed.
ColonyNetworkError,ColonyServerErrorandColonyNotFoundErrorall subclassColonyAPIError. A timeout defers the upvote, and a deleted post is dropped after 5 attempts as before. - The scope is as described. Only an upvote (
value > 0) is touched. Downvotes, JUNK, language, PII and mark-scanned are unaffected. Post text never reachescolony_analyzed.json, and a test checks that. - Local run. 305 passed, ruff clean. I also tried two faults beyond your eight, and the suite caught both:
- the re-read wrapped the way the live paths are (
{"post": ...}); - a blocked replay returning
False(deferred, so retried 5 times) instead ofTrue.
- the re-read wrapped the way the live paths are (
One non-blocking edge: a line that is just " is kept as a one-character phrase, and it then blocks every post that contains a double quote. It's unlikely anyone types that, so I'm only mentioning it.
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.
Adds
never_upvote_strings.txt: a local, gitignored list of phrases. If any of them appears in a post's title or body (case-insensitive substring), Sentinel never upvotes that post. It is the content-keyed sibling ofnever_upvote.txt.cp never_upvote_strings.txt.example never_upvote_strings.txt $EDITOR never_upvote_strings.txtScope: same as
never_upvote.txt. It suppresses upvotes only: no downvotes, and JUNK marking, language tags, PII flags and mark-scanned are untouched. Edits are picked up without a restart. The startup log reports it asnever-upvote-stringswith its absolute path, and the path can be overridden withSENTINEL_NEVER_UPVOTE_STRINGS_FILE.Two deliberate differences from the username lists
#. Double quotes allow a leading#or significant spaces. Empty or whitespace-only entries are dropped, because they would match every post.colony_analyzed.json._pending_actionstherefore re-reads the post before it is cast, and only when the list has entries. This also catches a post edited after its vote was queued.Also: the list cache is now keyed on (parser, path), so two lists pointed at the same file can't be served each other's parse.
Tests:
tests/test_never_upvote_strings.pyadds 28 tests. The full suite passes (305), and ruff is clean. I reintroduced each of these 8 faults on purpose, and each one failed the suite:🤖 Generated with Claude Code
https://claude.ai/code/session_01S635ZMrwXzoa93sGpwMhjM