Real projects · real findings
Bugs found. Automatically.
These issues were found by tailtest running alongside a Claude Code session. No test was written manually, tailtest generated the scenarios, ran them, and surfaced what failed.
Personal finance · open source
Recurring transactions permanently lose their date after February
Monthly transactions on the 29th, 30th, or 31st get clamped to Feb 28 when February arrives. On the next advance, the function reads the clamped day instead of the original, the intended date is permanently gone.
GTD productivity · open source
Monthly calendar events set by weekday appear on the wrong date
Events set to repeat on "the first Monday of the month" use a fixed day number instead. A meeting every first Tuesday shows up on the 6th of each month, regardless of what day that is.
Docs drift detector · open source
A file that trips both staleness thresholds is penalised twice
When a file exceeds both the day threshold (≥90 days) and the commit threshold (≥200 commits), checkStaleness returns two separate STALE_FILE errors for the same condition. computeScore deducts 10 points per error, so one stale file silently subtracts 20 points instead of 10.
AI-native code editor · open source
Monthly backup retention uses a fixed 30-day month, deleting backups prematurely
pruneOldBackups calculates the monthly cutoff as monthlyMonths × 30 days. In a 31-day month, a backup from 2 calendar months ago is 61 to 62 days old, past the 60-day cutoff, and gets deleted even though it should be kept.
Code complexity analysis · Mozilla
Two filter bugs corrupt comment stripping and function-body extraction
MinimalFilter: a single-line """...""" docstring leaves in_docstring=true, so a # comment between two single-line docstrings is incorrectly stripped. AggressiveFilter: lines starting with fn foo() { skip brace counting, causing let bindings after the first body line to leak out. 2 issues filed.
AI survey framework · open source
uniquify() produces duplicate IDs when suffixed names already exist in the list
When a ScenarioList contains a value like "item_1" and uniquify("id") is called on a list that also has duplicate "item" entries, the function generates "item_1" as a suffix, colliding with the pre-existing "item_1". The result has duplicate IDs, breaking the uniqueness contract.
Interactive CLI library
Module-level REDIRECTION_TOKENS list grows without bound on every alias or macro command
Four functions in cmd2.py (_alias_create, _alias_list, _macro_create, _macro_list) assign constants.REDIRECTION_TOKENS by reference and call .extend() on it, permanently mutating the module-level list. Each invocation appends terminators; after 5 alias creates the list grows from 3 to 10 entries. Two Cmd() instances in the same process share the corrupted state, instance A's alias operations affect instance B's redirection parsing.
CLI for Jinja2 rendering
KeyError leaks from get_format() and has_format() when the format string is unknown
get_format(fmt) only catches ModuleNotFoundError; when fmt is not present in the formats dict (e.g. 'nonexistent'), formats[fmt] raises KeyError which propagates uncaught instead of being wrapped as InvalidDataFormat. has_format() inherits the same root cause: it only catches InvalidDataFormat, so the KeyError leaks through and has_format("unknown") raises instead of returning False. Found via V13 native adversarial mode (depth: adversarial) on 2026-04-25.
Type-driven dependency injection
Module discovery crashes on PEP 420 namespace packages and traverses hidden dirs like .git
_find_objects_in_module accesses module.__file__ unconditionally, but PEP 420 namespace packages have no __file__ and crash with AttributeError. Three more robustness issues in the same file: endswith('__init__.py') false-positives on not__init__.py; hidden directories like .git and .mypy_cache are traversed and importlib.import_module is called with invalid dot-prefixed names; one broken submodule kills the entire discovery scan because there is no try/except around the import.
CLIs from type hints
Forward-ref resolution crashes when run from __main__ context
resolve_forward_ref calls __builtins__.copy(). In regular Python modules, __builtins__ is a dict; in __main__ and many embedding contexts it is the builtins module, and modules don't have a copy method. Calling jsonargparse from a __main__ script crashes. Four more edge-case crashes in the same file: bare tuple/set types (no __origin__), and Dict[int, str] with non-numeric or empty-string keys (raw ValueError leak from int()).
OpenAPI for Flask
Single-line docstring descriptions silently vanish from generated OpenAPI specs
get_operation has a backward condition: lines[0] if len(lines) == 0 else '<br/>'.join(lines[1:]). split() always returns at least one element, so len(lines) == 0 is always False, single-line docstrings always go through the join branch and produce an empty string. The description is silently lost. Two more bugs in parse_method: routes registered with HEAD or OPTIONS are silently dropped because the if/elif ladder only handles GET/POST/PUT/PATCH/DELETE.
Self-hosted DOCSIS evidence system
api_restore proceeds with no config_manager and writes to a hardcoded /data path
When get_config_manager() returns None, api_restore does not bail early. It falls through to a fallback that uses '/data' as the data directory and proceeds to extract the user's uploaded archive into a hardcoded /data path on the host filesystem. The sibling api_backup_download endpoint correctly returns 500 in the same condition.
Terminal radio player
Newline in alias name corrupts the alias file with phantom radio entries
add_entry writes name and url directly to the alias file with no sanitization. A name containing a newline (TUI input glitch, clipboard paste, scripted alias creation) splits across two lines and creates a phantom alias on the next read. The search() method also has a state leak: self.found is set to True on a hit but never reset to False on a miss, so any miss following a previous hit reports a stale True.
Persian / Jalali date utilities
JalaliDateTime copy-construct silently drops timezone info
JalaliDateTime(other_jdt) returns a naive copy when the source is timezone-aware. The constructor's two input branches behave asymmetrically: the stdlib datetime branch preserves tzinfo, the JalaliDateTime branch (Pickle-support path) does not. Round-tripping a tz-aware value through the constructor silently makes it naive.
English plural / singular / number-to-words inflection
number_to_words leaks IndexError instead of NumOutOfRangeError for huge inputs
engine().number_to_words(n) is documented to raise NumOutOfRangeError for out-of-range inputs (the exception is imported and advertised). For numbers requiring magnitude index >= 12 (roughly 10^36 and above), an uncaught IndexError leaks from internal mill[mindex] lookup in tenfn. Users wrapping calls in try/except NumOutOfRangeError will not catch IndexError, so their code crashes unexpectedly on large inputs.
.docx → HTML / Markdown converter
Unmatched w:fldChar end crashes convert_to_html with IndexError
read_fld_char calls complex_field_stack.pop() unconditionally when it encounters a fldChar of type end or separate. Well-formed documents always pair these with a prior begin, but a hand-edited, tool-generated, or partially recovered .docx can carry an unmatched end. The result is IndexError: pop from empty list leaking from a public conversion call, callers wrapping mammoth in a try/except for known docx errors see an unexpected stdlib exception instead. Same shape as the maintainer-fixed #158 (w:ilvl crash on malformed numbering). Suggested fix: guard both pops with an empty-stack check, return _empty_result.
ISO 8601 dates and durations · open source
Equal Durations hash differently, breaking sets and dicts
Duration.__eq__ treats Duration(years=1) and Duration(months=12) as equal (it normalizes years to months), but __hash__ hashes the raw year/month fields without that normalization. This violates Python's invariant that equal objects must hash equal, so equal Durations are not deduplicated in set/dict and membership tests silently fail.
Human-friendly numbers and dates · open source
naturaldelta(inf) crashes instead of returning the value unchanged
naturaldelta()'s docstring says a non-finite float is returned unchanged. nan is handled (its int() raises ValueError, which is caught), but float('inf') raises an uncaught OverflowError because the except clause omits OverflowError. That asymmetric crash contradicts the documented contract.
Schema validation · open source
Optional mutable default ([]/{}) leaks across every validate() call
An Optional key with a mutable default returns the SAME object on every validate(). Mutating one validation's result corrupts the default for all later validations of the same Schema. That is the classic shared mutable default footgun, leaking state between unrelated calls.
PEG parser · open source
OneOrMore over a zero-width match fails at end-of-input (off-by-one)
A '+' quantifier over a member that can match empty (e.g. the regex ~"a*") fails when the cursor is already at end-of-input: the match loop guard 'while new_pos < size' refuses to try the sub-match at new_pos == size, even though it would match zero-width there and satisfy min=1. Parsing then wrongly raises ParseError.
Local timezone detection · open source
Malformed system clock config crashes timezone detection
A /etc/sysconfig/clock line like ZONE="America/New_York with no closing quote makes the closing-quote regex return None; the code then calls .start() on it, raising an uncaught AttributeError. It isn't in the surrounding except (OSError, UnicodeDecodeError), so one malformed config line crashes detection instead of being skipped.
Domain and TLD extraction · open source
reverse_domain_name prepends a spurious dot for IPs and unknown TLDs
reverse_domain_name joins [suffix, domain, ...] without dropping empty components, so when suffix is '' (IP addresses, unknown TLDs) the result has a leading dot. The sibling fqdn property filters empties; reverse_domain_name does not.
Emoji handling · open source
emojize() validates the variant argument only for some inputs
emojize's docstring says it raises ValueError for an invalid variant. But the check runs lazily inside the per-match callback, only for an emoji that has a variant key, so the same invalid variant is silently accepted when the string has no emoji (or only emoji without a variant), and rejected otherwise.
HTML to Markdown · open source
Unicode 'numeric' characters in start/colspan crash int() conversion
convert_li and the table converter call int() on numeric-looking attribute values, but characters like '½' and superscript digits pass str.isnumeric() while int() rejects them, raising ValueError mid-conversion. tailtest surfaced both cold, with no guidance.
Functional utilities · open source
tail(0, seq) returns the whole sequence instead of nothing
tail(n, seq) is seq[-n:] with a deque fallback. For n=0, seq[-0:] is seq[0:], the entire sequence, so tail(0, [10,20,30]) returns everything instead of the last zero elements. It is also inconsistent: sliceable inputs return the whole sequence, non-sliceable iterables return empty.
URL parsing and manipulation · open source
Missing regex anchor lets invalid URL schemes through and corrupts the parse
is_valid_scheme matches the scheme regex but omits the trailing $ anchor (the module's other validators all use ^...$). Any prefix that looks like a scheme is accepted, so 'a b:c' is mis-parsed as scheme 'a b' plus path 'c' instead of no scheme and path 'a b:c'.
Style-preserving TOML · open source
item() reorders keys even with the default sort_keys=False
A misplaced parenthesis in item()'s sort key, (isinstance(i[1], dict), i[0] if _sort_keys else 1), leaves the dict-vs-scalar term always active, so dict-valued keys are forced after scalar keys even when sort_keys is False. The sibling top-level branch guards the whole tuple correctly.
Nested data access · open source
Path indexing one-past-the-end returns an empty Path instead of IndexError
Path.__getitem__ uses 'start > len(...)' instead of '>=' for its integer-index bounds check, so an index exactly one position past the end slips through and silently returns an empty Path() rather than raising IndexError.
Fuzzy string matching · open source
dedupe() crashes on an item that the default processor reduces to empty
dedupe() calls extractBests(score_cutoff=threshold) per item then max(matches). When an item reduces to the empty string under the default processor (e.g. '###'), all comparisons score 0, extractBests returns [], and max([]) raises ValueError. The code even warns about the empty-query case, then calls max() unconditionally.
Data validation · open source
Number validator leaks a raw TypeError on Infinity/NaN instead of raising Invalid
The Number validator converts input to Decimal and inspects precision/scale. For Decimal('Infinity')/Decimal('NaN'), that inspection raises a raw TypeError ('infinity and NaN have no precision') which escapes the validator instead of being surfaced as a voluptuous Invalid.
Async URL library · open source
URL.join() decodes percent-encoded characters in the base path
When joining a relative reference onto a base whose path does not end in '/', the RFC 3986 merge is built from the already-decoded path segments (self.parts), so an encoded delimiter like %2F is turned into a real '/' and the path structure is corrupted. The encoded segment should be preserved.
ASCII table rendering · open source
from_html() crashes on a ragged table row (off-by-one padding)
from_html() pads short rows up to the header width, but the padding loop iterates range(1, appends) instead of range(1, appends + 1), so a data row with fewer <td> cells than the header is padded one cell short and add_row rejects it with ValueError. Normalizing ragged HTML tables is exactly what from_html exists to do.
Table formatting · open source
tabulate() raises TypeError when rowalign is a tuple
Every sequence-valued parameter (headers, colalign, floatfmt, maxcolwidths) accepts a tuple, but rowalign/maxheadercolwidths do not: the internal padding helper does 'original + [default] * n', which is tuple + list when a tuple is passed, raising TypeError (even when no padding is needed).
Functional utilities · open source
to_list() on a dict returns dict_values, not a list
A function named to_list returning a non-list breaks isinstance(result, list) and any code that indexes or concatenates the result. For a dict input, to_list yields obj.values() without materializing it into a list.
Immutable data structures · open source
CheckedPMap.create drops ignore_extra for nested checked types
CheckedPMap.create accepts ignore_extra but does not forward it to the nested key/value CheckedType.create calls, so extra fields in a nested checked value still raise. CheckedPVector and CheckedPSet thread ignore_extra correctly; CheckedPMap is the one collection that omits it.
Schema validation · open source
Any validator can pass when every subvalidator fails
Any (succeed if any subvalidator passes) decides success by comparing the number of collected messages to the number of validators. A failure raised as Invalid(node) with no message contributes zero messages, so when all subvalidators fail but one is message-less, the count comes up short and Any silently treats it as a pass, a false-accept in validation.
Object serialization · open source
decode() crashes on a 6-element py/reduce payload
A 6-tuple is a valid __reduce_ex__ result (protocol 5 added a 6th state_setter element), but the unpickler only pads tuples shorter than 5 and then unpacks exactly five names, so a 6-element reduce raises an uncaught ValueError instead of being handled.
Full sweep
Every repo we've tested
38 bugs confirmed across 95 repos swept (2 fixed and merged by maintainers within 24 hours of filing, 20 reported upstream this cycle). The clean results are signal too, well-tested code passes.
| Repo | Result |
|---|---|
| mattrobenolt/jinja2-cli | Bug filed #145 |
| python-cmd2/cmd2 | Bug filed #1649 |
| maldoinc/wireup | Bug filed #135 |
| omni-us/jsonargparse | Bug filed #904 |
| luolingchun/flask-openapi | Bug filed #262 |
| itsDNNS/docsight | Bug filed #357 |
| deep5050/radio-active | Bug filed #150 |
| securo-finance/securo | Bug filed #67 |
| dongdongbh/Mindwtr | Bug filed #380 |
| theDakshJaitly/mex | Bug filed #31 |
| paperclipai/paperclip | Bug filed #3713 |
| rtk-ai/rtk | Bug filed #1322 |
| expectedparrot/edsl | Bug filed #2446 |
| majiidd/persiantools | Bug filed #62 |
| mwilliamson/python-mammoth | Bug filed #168 |
| hukkin/tomli | No findings |
| frostming/marko | No findings |
| pallets-eco/croniter | No findings |
| akoumjian/datefinder | No findings |
| jannikmi/timezonefinder | No findings |
| JoshData/python-email-validator | No findings |
| encode/httpcore | No findings |
| un33k/python-slugify | No findings |
| python-validators/validators | No findings |
| Suor/funcy | No findings |
| davidhalter/parso | No findings |
| pyparsing/pyparsing | No findings |
| marshmallow-code/marshmallow | No findings |
| more-itertools/more-itertools | No findings |
| getcompanion-ai/feynman | No findings |
| basicmachines-co/basic-memory | No findings |
| sgcarstrends/backend | No findings |
| berrydev-ai/blockdoc-python | No findings |
| stefanjudis/cchooks | No findings |
| lamoom-ai/lamoom | No findings |
| badlogic/claude-code-tools-python | No findings |
| claudekit-dev/claudekit | No findings |
| rulesync | No findings |
| vibe-log-cli | No findings |
| tdd-guard | No findings |
| cc-flow | No findings |
| claude-hub | No findings |
| tsk | No findings |
| claude-squad | No findings |
| aws-mcp | No findings |
| gweis/isodate | Bug filed #104 |
| python-humanize/humanize | Bug filed #333 |
| keleshev/schema | Bug filed #352 |
| erikrose/parsimonious | Bug filed #257 |
| regebro/tzlocal | Bug filed #181 |
| john-kurkowski/tldextract | Bug filed #370 |
| carpedm20/emoji | Bug filed #330 |
| pytoolz/toolz | Bug filed #626 |
| gruns/furl | Bug filed #192 |
| python-poetry/tomlkit | Bug filed #546 |
| mahmoud/glom | Bug filed #299 |
| seatgeek/thefuzz | Bug filed #94 |
| alecthomas/voluptuous | Bug filed #541 |
| xmltodict | No findings |
| python-dotenv | No findings |
| boltons | No findings |
| python-decouple | No findings |
| feedparser | No findings |
| charset_normalizer | No findings |
| pint | No findings |
| aio-libs/yarl | Bug filed #1774 |
| prettytable/prettytable | Bug filed #474 |
| astanin/python-tabulate | Bug filed #434 |
| dgilland/pydash | Bug filed #249 |
| tobgu/pyrsistent | Bug filed #326 |
| Pylons/colander | Bug filed #371 |
| jsonpickle/jsonpickle | Bug filed #608 |
| arrow | No findings |
| cerberus | No findings |
| simplejson | No findings |
| tenacity | No findings |
| chardet | No findings |
| configobj | No findings |
| ccpm | Skipped |
| coffee-analytics | Skipped |
Skipped = no testable source code (skills-only or SQL-only repos)
Get started
Run it on your own project.
Install in 60 seconds. No test files to write, no configuration, no commands.