2026-08-04 The Self-Exclusion Glob That Outlived Its Filename

What I set out to do

A check-upstream-issues run over ~/.config came back with two pieces of noise and one actionable result: a warning about a shorthand #456 tag with no --repo, a gh: Not Found (HTTP 404) for org/repo#123, and one resolved issue (astral-sh/ty#1737, closed 12 days prior) whose workaround could presumably be deleted. Fix both.

What I actually did

The warning and the 404 were the same bug, and it was self-inflicted. Both came from the tool’s own docstring in nix/projects/check-upstream-issues/src/check_upstream_issues/cli.py, which spells the two accepted tag forms out literally as help text. The scanner has always excluded its own source and reference doc via ripgrep globs — but the globs were !check-upstream-issues.py and !check-upstream-issues.md. The .md one still matches. The .py one names a file that stopped existing when the PEP 723 scripts became uv workspaces under nix/projects/ (see 2026-08-04 uv Project Registry and the devTools Gate). A path-based self-exclusion is exactly the kind of thing a restructure breaks silently: the tool kept running, kept exiting 0, and just started reporting its own examples. Replaced it with !**/check_upstream_issues/cli.py and wrote the integration test that would have caught the rename.

The resolved issue was resolved, but the workaround wasn’t removable. ty#1737 (“Support generic manual PEP 695 type aliases”) is closed as completed, not stale-bot’d, and the local comments claimed a fix in ty 0.0.63 against a nixpkgs-pinned 0.0.61. nixpkgs now ships 0.0.65, so on paper both tagged workarounds in permission-suggestion were dead weight. Removing the cast(SuggestionAgent, Agent(...)) in agent.py immediately produced a new invalid-return-type, so the cast was still load-bearing — the interesting question was why.

Bisected it with throwaway probes rather than guessing. A bare TypeAliasType with type_params, both simple and recursive, now resolves correctly under 0.0.65 — #1737 really is fixed. What still fails is passing a value typed as OutputSpec[list[T]] into Agent(output_type=...): the OutputDataT binding is lost, no __init__ overload matches, and the return type collapses to Agent[AgentDepsT, OutputDataT]. Different bug, same symptom.

The fix was to stop routing through pydantic-ai’s OutputSpec at all. _wrap_output_type only ever returns three shapes, so annotating it as type[...] | NativeOutput[...] | PromptedOutput[...] lets inference succeed — which removed the cast and a pre-existing no-matching-overload error that the cast had been masking all along. The second tag, on _ExpectedRaw in eval_suggestions.py, turned out to guard nothing: the comment said to keep the assignment alias instead of a PEP 695 type statement, but the project’s requires-python = ">=3.11" forbids type regardless of what ty supports. Deleted the tag, kept the alias, added no replacement comment — a plain assignment alias is just ordinary Python.

What was striking

Three separate things wore the same disguise.

The stale glob is the sharper lesson. check-upstream-issues is the tool for noticing that a workaround has outlived its reason, and the mechanism it uses to avoid reporting itself had itself gone stale — quietly, because a no-longer-matching exclusion glob has no failure mode. It just stops excluding. Nothing in just check or nix flake check would ever say so.

And a cast is a lie that keeps being told after it stops being true. That one had a precise, dated, correct-sounding comment attached — “fixed in 0.0.63, still needed at 0.0.61” — and the comment was wrong about which bug was holding the line. Deleting the cast to see what breaks was worth more than reading it. The narrower union that replaced it is also just better typing: OutputSpec was always wider than what the function can return.

Deployed via hm switch; the run is clean at 0 resolved, 17 open.

A pre-existing sum() overload error in eval_suggestions.py:322 was keeping permission-suggestion’s just check red independently of any of this (confirmed via stash that it predates the session). ty doesn’t narrow row[idx] through a comprehension’s if ... is not None filter, so applicable stayed list[bool | None]; binding the value with a walrus makes the narrowing stick. Worth noting that the fix is the same species as the OutputSpec one — in both cases the type was wider than the values could actually be, and the repair was to say the narrower thing rather than to suppress the complaint.

Shipped as two commits: 12b1514 for the scanner glob, 1c512fd for the ty workaround removal.