Review this new GitHub Actions workflow and its two supporting Python scripts for security and correctness bugs. This workflow is meant to run with NO secrets at all — it only reads already-posted (already-redacted) PR review comments and a repo-local archive file, then decides whether to open a GitHub issue. It must never be able to merge, deploy, or exfiltrate anything.
Focus specifically on:
- Trust boundary: does checkout/execution ever run PR-controlled code with elevated permissions? (workflow_run should check out the default branch, not the PR head — verify this claim is actually true of how workflow_run behaves, and that nothing in this file overrides that default.)
- Could a malicious PR author craft a PR comment (or otherwise) that causes this workflow to create issues, spam, or perform any other unintended action, given it has issues:write permission and parses comment text?
- Logic bugs in the idempotency check for issue creation (race conditions, title collisions, pagination).
- Bugs in the two Python scripts' threshold-crossing logic.
Output findings in the format: N. [P1|P2] description (one line each). If nothing found, say so plainly.
---WORKFLOW---
name: Archive and Recommend
Closes the specific gap named in docs/production-hardening-backlog.md
item #4's self-improvement work: scripts/detect-recurring-pattern.py could
already derive a target-vs-mechanism recommendation from
docs/self-improvement-archive.jsonl's accumulated evidence, and
scripts/analyze-latest-review.py could tell whether the LATEST round is
the one that newly tips a topic over the mechanism-fix threshold -- but
something still had to run those tools and act on the result. That "when
to act" decision was a human/agent judgment call made by reading the
archive. This workflow makes it automatic, but ONLY for a deliberately
low-risk, reversible action: opening a tracking issue. It has no
permission to merge, deploy, or touch secrets, and requests none.
Runs after "Codex Review" (.github/workflows/codex-review.yml) completes.
Deliberately uses workflow_run, not pull_request or workflow_run's
head ref: workflow_run always executes the workflow file AND checks out
source from the repository's default branch, never the PR's own commits
-- so, unlike codex-review.yml, this workflow has no PR-authored-script
trust boundary to manage. It also requests no secrets at all: everything
it reads (the posted review comment, the archive file) is already public,
already-redacted PR content.
on:
workflow_run:
workflows: ["Codex Review"]
types: [completed]
permissions:
contents: read
issues: write
pull-requests: read
concurrency:
group: archive-and-recommend-${{ github.event.workflow_run.id }}
cancel-in-progress: false
jobs:
analyze:
name: Analyze review for recurring patterns
runs-on: ubuntu-latest
if: github.event.workflow_run.pull_requests[0] != null
steps:
- name: Checkout (default branch — trusted)
uses: actions/checkout@v4
with:
persist-credentials: false
---analyze-latest-review.py---
#!/usr/bin/env python3
"""Decide, from evidence, whether a just-completed review round newly
crosses the mechanism-fix threshold for any finding topic.
This is the piece that closes the gap named while building this archive:
scripts/detect-recurring-pattern.py could already derive a target-vs-
mechanism recommendation from the archive's accumulated data, but something
still had to run it and decide whether the result was worth acting on --
that was a human/agent judgment call, made by eyeballing the tool's output.
This script makes that specific decision mechanical: it compares the
recommendation with vs. without the latest round's findings included, and
reports only topics whose recommendation flips from "target" to
"mechanism" (or newly appears at/above threshold) because of this round
specifically -- not topics that already crossed the threshold in earlier
rounds, which would otherwise fire on every single subsequent round
forever. A workflow can run this automatically after every review and act
(e.g. open a tracking issue) purely on its output, with no one needing to
have read the archive and noticed the pattern themselves.
Usage:
python3 analyze-latest-review.py <archive.jsonl> <review-comment.txt> [--threshold N]
Exits 0 always (advisory). Prints newline-delimited human-readable lines,
then a --- separator, then a JSON object: {"newly_crossed": [...]}.
"""
from future import annotations
import argparse
import importlib.util
import json
import sys
from pathlib import Path
def _load_sibling_module(name: str, filename: str):
path = Path(file).parent / filename
spec = importlib.util.spec_from_file_location(name, path)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
return module
detect = _load_sibling_module("detect_recurring_pattern", "detect-recurring-pattern.py")
parse_findings_mod = _load_sibling_module("parse_review_findings", "parse-review-findings.py")
def load_archive(path: str) -> list[dict]:
entries = []
with open(path) as f:
for line in f:
line = line.strip()
if line:
entries.append(json.loads(line))
return entries
def next_round_number(entries: list[dict]) -> int:
rounds = [e.get("round", 0) for e in entries]
return (max(rounds) + 1) if rounds else 1
def recommendations_by_topic(entries: list[dict], threshold: int) -> dict[str, str]:
result = detect.analyze(entries, threshold)
return {rec["topic"]: rec["recommended_action"] for rec in result["recommendations"]}
def find_newly_crossed_topics(
archive_entries: list[dict], new_findings: list[str], threshold: int
) -> list[dict]:
"""Compare recommendations with vs. without the new round's findings.
def main(argv: list[str]) -> int:
parser = argparse.ArgumentParser(description=doc)
parser.add_argument("archive_path")
parser.add_argument("review_comment_path")
parser.add_argument("--threshold", type=int, default=detect.DEFAULT_THRESHOLD)
args = parser.parse_args(argv[1:])
if name == "main":
sys.exit(main(sys.argv))
---detect-recurring-pattern.py (analyze function)---
#!/usr/bin/env python3
"""Decide target-fix vs. mechanism-fix from the improvement archive itself.
Closes the specific gap named while building
docs/self-improvement-archive.jsonl: across rounds 1-5, I (the agent)
judged when a finding was serious/recurring enough to warrant revising the
improvement mechanism itself (round 5) rather than just patching the current
target again (rounds 2-4). That judgment call was mine, not something
derived from the archive's own data — which is exactly the gap the paper's
L5 definition ("persistently revises a mechanism that governs subsequent
improvement") requires closing: the recursion has to include deciding when
to recurse on the mechanism, not just executing that decision once someone
notices a pattern.
This script makes that decision algorithmically instead: it reads the
archive, buckets every finding into a topic by keyword co-occurrence (no ML,
no external calls -- deliberately simple and auditable), and recommends
"mechanism" once a topic has recurred at or above a threshold across
distinct rounds, "target" otherwise. It is still a human (or an agent
executing on the human's behalf) who reads the recommendation and acts on
it -- this does not make merge/deploy autonomous, and does not claim to.
What it closes is narrower and real: the decision itself is now
reproducible from evidence, not from an agent's unrecorded judgment.
Usage:
python3 detect-recurring-pattern.py <archive.jsonl> [--threshold N]
Prints one recommendation line per topic that has reached the threshold,
plus a machine-readable JSON summary to stdout after a --- separator.
Exit code 0 always (this is advisory, not a pass/fail gate).
"""
from future import annotations
import argparse
import json
import sys
from collections import defaultdict
DEFAULT_THRESHOLD = 3
Deliberately simple, auditable keyword buckets rather than an embedding
model or external call -- the point of this tool is that its decision
process is itself inspectable, matching the same "no unexplained black
box" standard the rest of this archive holds code changes to.
TOPIC_KEYWORDS: dict[str, list[str]] = {
"credential-redaction": ["redact", "credential", "secret", "token", "leak", "expos"],
"shell-semantics": ["errexit", "bash -e", "exit code", "-e", "pipefail", "shell"],
"env-var-precedence": ["precedence", "env var", "environment variable", "unconditionally"],
"fork-pr-permissions": ["fork", "github_token", "persist-credentials"],
"auth-lifecycle": ["refresh token", "rotat", "expir", "auth.json", "stale"],
}
def classify_finding(text: str) -> str | None:
lowered = text.lower()
for topic, keywords in TOPIC_KEYWORDS.items():
if any(kw in lowered for kw in keywords):
return topic
return None
def load_archive(path: str) -> list[dict]:
entries = []
with open(path) as f:
for line in f:
line = line.strip()
if line:
entries.append(json.loads(line))
return entries
def analyze(entries: list[dict], threshold: int) -> dict:
topic_rounds: dict[str, set[int]] = defaultdict(set)
topic_examples: dict[str, list[str]] = defaultdict(list)
def main(argv: list[str]) -> int:
parser = argparse.ArgumentParser(description=doc)
parser.add_argument("archive_path")
parser.add_argument("--threshold", type=int, default=DEFAULT_THRESHOLD)
args = parser.parse_args(argv[1:])
if name == "main":
sys.exit(main(sys.argv))