From 01097da22c65fc613fcc8f5b15de24844fde5d77 Mon Sep 17 00:00:00 2001 From: Teleo Agents Date: Wed, 13 May 2026 02:39:18 +0000 Subject: [PATCH] fix(activity-feed): canonicalize contributor handle so profile links resolve MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The activity feed was returning decorated strings like "Vida (self-directed)" and "@m3taversal" in the contributor field. The frontend uses that field as both display label and routing handle, so /contributors/Vida%20(self-directed) 404s — Next fires notFound() in [handle]/page.tsx. Root cause: _normalize_contributor only stripped @ and whitespace; it did not lowercase or strip the " (self-directed)" suffix that extract.py and the older backfill_submitted_by.py wrote into prs.submitted_by. Mixed-case agent names (Vida vs vida) and pipeline decorators ("pipeline (reweave)") both fell through. Fix: lowercase + strip any trailing parenthetical decorator. Valid handles match ^[a-z0-9][a-z0-9_-]{0,38}$ per attribution._HANDLE_RE and cannot contain parens, so the strip is lossless. DB simulation against 3612 merged-PR events: 0 orphan handles after normalization (was 12 orphan label-variants before). No KB writes — pure read-side normalization in the API layer. --- diagnostics/activity_feed_api.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/diagnostics/activity_feed_api.py b/diagnostics/activity_feed_api.py index acc5ec9..c901ee7 100644 --- a/diagnostics/activity_feed_api.py +++ b/diagnostics/activity_feed_api.py @@ -129,12 +129,33 @@ def _github_pr_url(github_pr): return f"https://github.com/living-ip/teleo-codex/pull/{github_pr}" +# Canonicalize contributor labels so frontend links resolve to real +# /contributors/{handle} pages. Pipeline writers (extract.py, manual edits, +# the old backfill_submitted_by.py) historically wrote mixed-case agent +# names with a trailing decorator into prs.submitted_by — e.g. +# "Vida (self-directed)", "pipeline (reweave)", or "@m3taversal". +# These decorated strings do not exist as contributors and 404 the profile +# page. Strip the trailing parenthetical wholesale: valid handles match +# ^[a-z0-9][a-z0-9_-]{0,38}$ (see pipeline/lib/attribution._HANDLE_RE) and +# cannot contain parens, so this is lossless. +_TRAILING_PAREN_RE = re.compile(r"\s*\([^)]*\)\s*$") + + +def _canonicalize(raw): + if not raw: + return "" + h = raw.strip().lower().lstrip("@") + h = _TRAILING_PAREN_RE.sub("", h).strip() + return h + + def _normalize_contributor(submitted_by, agent): - if submitted_by and submitted_by.strip(): - name = submitted_by.strip().lstrip("@") + name = _canonicalize(submitted_by) + if name: + return name + name = _canonicalize(agent) + if name and name != "pipeline": return name - if agent and agent.strip() and agent != "pipeline": - return agent.strip() return "pipeline"