Search

Adding ht-github-issue-rollout to hermes-talaria — Separating API Visibility from Administrative Coverage and Not Substituting Markdown Links for Native Sub-Issues

Tadashi Shigeoka · Thu, October 22, 2026

Organization-wide vulnerability response has become routine. A CVE is published, and within a few days every repository that depends on the affected package needs an investigation and a remediation request. I added a skill to hermes-talaria that drives this bulk rollout across a GitHub organization or an enterprise. The PR is codenote-net/hermes-talaria#62.

The skill name is ht-github-issue-rollout, invoked as /ht-github-issue-rollout. Targets are an organization URL of the form https://github.com/orgs/ORG/repositories or an enterprise URL of the form https://github.com/enterprises/ENTERPRISE/organizations. This post walks through the design decisions I made for it.

Overview of What the Skill Adds

  • A Hermes Agent skill that scans every repository in an organization, or every organization and repository in an enterprise
  • Assesses a shared request (vulnerability remediation, dependency migration, policy change, and similar) against each repository, and creates an approved native epic/sub-issue hierarchy
  • Organization mode is “organization epic → repository issues.” Enterprise mode is “enterprise epic → organization epics → repository tracking issues”
  • The design decisions are covered in the sections below (API visibility vs. administrative coverage, assessment states, the same-owner constraint, hidden-marker recovery)
  • Ships a read-only inventory helper in Python/gh. Assessment, approval, creation, and recovery stay in the agent-driven procedure

Why an Agent Procedure, Not GitHub Security Campaigns

GitHub Security Campaigns group Code Scanning and secret scanning alerts so they can be fixed together. What I wanted was a bulk request procedure that also covers dependency updates, policy changes, SaaS configuration migrations, and runtime moves (work that does not originate from those alerts).

A CVE itself is discoverable in the GitHub Advisory database or by npm audit, but deciding “which repositories are affected” requires inspecting manifests, lockfiles, runtime configuration, and nested workspaces. The skill hands that assessment, the approved native hierarchy creation, and the recovery procedure to an AI agent.

The skill does not perform the following, even to finish faster:

  • Running repository code
  • Applying remediation patches
  • Changing security settings
  • Leaking discovered vulnerability evidence into public repositories

Keep API Visibility and Administrative Coverage Separate

The first design clause I added is to treat “GraphQL pagination fully traversed with totalCount and pageInfo.hasNextPage” as distinct from “every repository in scope has been reached.”

GitHub’s GraphQL API results are scoped to the authenticated principal’s visibility. SSO, App installations, and per-repository permissions can leave a connection “next page: none” while hidden repositories still exist. In a vulnerability rollout, conflating the two produces reports that claim organization-wide coverage while silently passing over invisible repositories.

SKILL.md requires:

  • The helper’s pagination_complete is not evidence of administrative coverage
  • Reconcile with an authorized administrator’s inventory or explicit confirmation that this principal sees every repository in scope, comparing stable IDs and totals
  • If full coverage cannot be established, report the visible subset and stop before bulk creation
  • Re-enumerate immediately before freezing the write plan and reconcile additions, transfers, renames, deletions, and total drift by ID

For enterprise mode, discovery can require read:enterprise on a classic token; fine-grained and GitHub App tokens may not support the required enterprise query. If access is missing, the skill stops and asks for authorized setup rather than printing a token or silently substituting the viewer’s memberships.

Keep applicable, not_applicable, and unknown Distinct

The failure mode that bulk rollouts collapse into most easily is “fold investigations that could not complete into not applicable.” The report then claims every repository received a request, while production-affecting repositories whose scan failed were silently skipped.

SKILL.md splits assessment into three states:

StateMeaningDefault issue action
applicableEvidence meets the supplied criteriaCreate one actionable repo issue
not_applicableEvidence proves the criteria do not applySkip; retain reason in private inventory
unknownMissing access, incomplete evidence, or ambiguous criteriaInvestigate; never relabel as not applicable

Assessment inspects relevant manifests, lockfiles, configuration, nested workspaces, and, when authorized, security alerts, pinned to a specific commit. Build and install scripts are not executed during inspection. An empty search result or missing alert alone does not establish non-applicability. Archived, issues-disabled, and permission-blocked statuses are tracked on a separate axis from applicability, and unknowns must be resolved before publication.

When the user explicitly asks for issues on not-applicable repositories (for administrative tracking), the skill creates issues titled [Not applicable] with “Not applicable to this request — no remediation required” at the top of the body. These carry no remediation checklist, no deadline, and no vulnerable label, and their counts stay separate from the applicable set.

Keep a Native Hierarchy Under the Same-Owner Constraint

As of 2026-10-22, the sub-issue REST API documents that the sub-issue must belong to the same repository owner as the parent issue. An enterprise-wide hierarchy that spans organizations hits this constraint when built through the REST API.

A common workaround is to list “repositories to act on” as a Markdown link list in the parent issue body. That looks similar from the outside, but it is not a native sub-issue: the platform’s progress aggregation, paginated child listings, parent-change detection, and cycle prevention do not apply to Markdown links.

SKILL.md resolves this as follows:

  • For enterprise mode, offer a single approved centralized tracking repository and place the enterprise epic, every organization epic, and every repository tracking issue in that one repository
  • In each issue body, identify the organization and repository being represented
  • If the user requires that repository issues live in their original repositories, stop and explain that the documented API cannot guarantee a single native tree
  • Do not call a Markdown link list a native sub-issue, and do not auto-duplicate proxy issues to simulate one
  • Do not infer cross-owner edges from enterprise membership

Native relationships also have limits. According to the documentation on adding sub-issues, as of 2026-10-22 that is 100 sub-issues per parent and eight nested levels. SKILL.md requires measuring the current state (including existing children and ancestor depth) before any creation, and when the limit is exceeded, reporting the exact overflow and asking for an approved batched hierarchy or multiple roots. It does not truncate the inventory or silently change the requested tree.

Approve a Frozen Plan Before Any Write

The native hierarchy, tracking destinations, issue titles and bodies, labels, assignees, and order each break the meaning of approval if any one of them changes after the fact. SKILL.md requires a frozen plan containing:

  • A stable rollout key (for example ht-advisory-example) and node keys
  • Represented repository IDs for each node
  • Hosting repositories
  • Titles and bodies
  • Applicability
  • Parent node keys
  • Intended order
  • Labels and assignees
  • Issue actions

A post-approval change to the scope, hierarchy, destinations, counts, or issue text invalidates that approval and requires a new one. Audience is reviewed on every edge and body as well: private and internal repository names and vulnerability evidence must not leak into a tracking repository with broader access.

Unnecessary mass mentions are excluded. Only verified existing labels and valid assignees are used; labels and assignees are not created on the fly.

Recover from Partial Failure Through Hidden Markers

A rollout across tens to hundreds of repositories hits rate limits, network interruptions, and ambiguous HTTP responses in the middle. GitHub issue creation has no Idempotency-Key header, so “the POST was sent, the response never came back, and whether the issue was created cannot be inferred from the socket” is a routine state.

SKILL.md embeds this hidden marker in each new issue body:

<!-- ht-issue-rollout: ROLLOUT_KEY NODE_KEY -->

ROLLOUT_KEY and NODE_KEY are restricted to letters, digits, hyphens, underscores, and dots so untrusted raw text cannot land in a marker. Recovery lists all open AND closed issues in each hosting repository through paginated REST, excludes pull requests, and compares the complete hidden marker literally plus semantic duplicates:

  • Single match: treat as partial completion and resume creation and attachment from there
  • Multiple matches, closed candidates, incompatible body, existing parent, or semantically equivalent unmarked issues: require an explicit reuse/skip decision and a revised approved plan
  • Ambiguous creation response: reconcile with /issues?state=all by exact marker before any retry
  • Ambiguous attachment response: read both parent and child, and verify both directions of parent and paginated sub_issues
  • When recovery is not provable, stop with a recovery ledger. Do not auto-close, auto-detach, or auto-reparent as rollback

Only one writer may execute a given rollout key. Without an atomic create-idempotency primitive on GitHub’s side, concurrent writes let duplicates slip through.

Verify by Read-Back at Both the Issue and the Edge

An HTTP 201 does not prove that the submitted payload matches what GitHub stored. SKILL.md requires, for each created issue:

  • Immediately GET /repos/{owner}/{repo}/issues/{number} and compare title, body, state, labels, and assignees to the frozen payload
  • Use the REST integer id for attachment, never the issue number or the GraphQL node ID
  • After attachment, read the child’s /parent and slurp the parent’s /sub_issues across all pages, then confirm the relationship is visible from both directions
  • When the approved plan requires a display order, use PATCH /repos/{owner}/{repo}/issues/{number}/sub_issues/priority with after_id or before_id, and reread the full child list before and after each call to confirm the ID set matches the approved set

gh api --paginate --slurp returns a per-page array, so flatten in code before comparing IDs or counts. Exclude objects that contain a pull_request field. A 404 from the parent endpoint alone is not evidence of a failed attachment: establish that the issue, repository, endpoint, and permissions are reachable, then reconcile the parent’s children across pages.

Operations the Skill Does Not Perform

SKILL.md lists operations that are not performed even to finish faster:

  • Silently relocating issues across owners, or calling a Markdown link list native sub-issues
  • Unarchiving a repository or enabling Issues automatically
  • Overwriting an existing parent with replace_parent: true
  • Retrying creation after an ambiguous response without reconciling by exact marker
  • Rolling back by auto-close, auto-detach, or auto-reparent
  • Using enterprise membership, Code Search, or the current user’s memberships as an all-repositories inventory
  • Publishing security evidence into a tracking repository with broader audience than the source
  • Sanitizing a credential-bearing URL and continuing
  • Reporting enterprise-wide completion without read-back verification
  • Folding Code Scanning alert triage into this procedure and describing it as a replacement for GitHub Security Campaigns

That’s all from adding ht-github-issue-rollout to hermes-talaria and settling on a design that separates API visibility from administrative coverage, applicable from unknown, and native hierarchy from Markdown-link lookalikes, from the Gemba.

References