Repository navigation
report-skill-issue: check repository visibility before a report names a repository #103
Description
Goal
Make the docks report-skill-issue skill keep private repositories out of public reports by checking, not by memory. Before a draft names a repository, the agent reads its visibility with gh; the user's own project, when it is not public, contributes no owner, repository name, issue or pull request title, branch name, or #-reference to the draft. The pre-filing scan then checks the draft for those names. Also read the approved title from a file in stage 6 and in the prefilled-link scripts, as the body already is, so the shell passes the approved text unchanged.
Yolo: the user delegated every decision except the merge to the agent.
Mode: plan-and-implement
Research
Facts
- Repository fact: the skill's redaction rule (
plugins/docks/skills/productivity/report-skill-issue/SKILL.md, second constraint and "Check before filing") lists "names of private repositories, organizations, customers, and people" and "titles of private plans and issues", but it gives no way to decide whether a repository is private, and its scan finds only machine paths and token shapes ("It does not find a private name or a hostname; read the text for those."). - Repository fact: a report usually comes from the repository that the reporting agent works in, so that repository's name is the most likely private name in a draft.
- Repository fact:
gh issue developnames a linked branch<n>-<slug of the issue title>(for example this plan's branch), so a branch name from a private project carries a private issue title. - GitHub renders
#26in an issue as a link to issue 26 of the same repository, andowner/repo#26as a link to the named repository (https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/autolinked-references-and-urls). A number from a private project written as#123in a DocksDocks/docks issue therefore links to DocksDocks/docks#123, a different issue. gh repo view <owner/repo> --json visibilityprints the visibility (https://cli.github.com/manual/gh_repo_view). Experiment in the review scratch directory: it printedPRIVATEfor a private repository that the account can read,PUBLICfor DocksDocks/docks, andGraphQL: Could not resolve to a Repository with the name '<owner>/<name>'for a repository that does not exist; GitHub gives the same answer for a private repository that the account cannot read. With an explicit<owner>/<repo>argument, the lookup named that repository also withGH_REPOset to another one. A bare<owner>/<repo>takes its host from gh's default host, whichGH_HOSTsets (https://cli.github.com/manual/gh_help_environment): withGH_HOST=example.invalid,gh repo view DocksDocks/dockstried to connect to that host, whilegh repo view github.com/DocksDocks/docksprintedDocksDocks/docks PUBLIC.- Without an argument,
gh repo viewresolves the repository from gh's own choice, not from a stated remote:GH_REPOcan select another repository for commands that operate on a local repository (https://cli.github.com/manual/gh_help_environment), andgh repo set-defaultpicks which remote gh queries in a checkout with several remotes (https://cli.github.com/manual/gh_repo_set-default). In gh 2.102.0 the argument-freegh repo viewstill showed the local repository withGH_REPOset (experiment), but the default-remote choice can still name an upstream repository instead of the project's own remote. - GitHub keeps every revision of an edited issue or comment, and anyone who can read the issue can open them; a user with write access can delete a revision in the web UI, and the API has no call for it (https://docs.github.com/en/communities/moderating-comments-and-conversations/tracking-changes-in-a-comment; the GraphQL schema has no mutation for content edits).
- Repository fact: stage 6 runs
gh issue create --repo DocksDocks/docks --title "<approved title>" --body-file "$body", and the prefilled-link scripts take"<approved title>"as an argument. The skill already says to use--body-file"because the shell can change backticks,$, and quotes in inline text"; the title has the same problem, so gh can file a title that differs from the one the user approved. Experiment with a fakeghthat prints its arguments: a title with a word in backticks arrived without that word. With the title in a file and--title "$(cat "$title")", a title with a word in backticks and$HOMEarrived unchanged, because Bash does not expand the result of a command substitution again (https://www.gnu.org/software/bash/manual/html_node/Command-Substitution.html).
Decisions
- Read the title from a file. Stage 6 writes the approved title to a temporary file, as it writes the body, and runs
gh issue create --repo DocksDocks/docks --title "$(cat "$title")" --body-file "$body"; both files are removed after filing. The Node and Python prefilled-link scripts take the title file path instead of the title text. Rejected: a single-quoted inline title (an apostrophe in the title breaks it);gh apiwith-F title=@<file>(another command and another output, whilegh issue createalready prints the URL). - Look up visibility before the draft names a repository. A new paragraph in stage 4 tells the agent to resolve the user's project from its remotes, not from gh's defaults: list
git remote -vin the project, take the host, owner, and name from each remote URL, and for eachgithub.comremote rungh repo view github.com/<owner>/<repo> --json visibility --jq .visibility, with the host written out so that neitherGH_REPOnorGH_HOSTcan change the target. The project counts as public only when it has at least one remote, every remote is ongithub.com, and every lookup printsPUBLIC. No remote, a remote on another host, a URL that does not parse, or a failed lookup counts as not public. When the project is not public, the draft leaves out every remote's owner and name, the project's issue and pull request titles, and its branch names, and writes its issue and pull request numbers without#(for example "issue 123 in a private repository"). Every other repository that the draft names keeps its name only when the same host-qualified lookup printsPUBLIC; a repository on another host, or a failed lookup, counts as private. Without gh, every repository other than DocksDocks/docks counts as private. The second constraint adds one sentence: decide visibility with that lookup, not from memory. Rejected: the argument-freegh repo view(it follows gh's default-repository choice, not the project's remotes) and a bare<owner>/<repo>(it followsGH_HOST). - Scan the draft for the project's names. "Check before filing" adds two commands next to the existing scan: one prints each line of the title and body files that holds an owner or repository name from the project's remotes, as stage 4 resolved them (for a private project it must print nothing); one lists every GitHub repository reference (
github.com/<owner>/<repo>and<owner>/<repo>#<n>) for the visibility lookup. The checklist item for private names points to them. - Repair a filed issue that names a private repository. A short paragraph after stage 6: edit the issue or comment to remove the name, then tell the user to delete the old revision in the web UI ("edited", then the revision, then "Delete revision from history"), because GitHub keeps every revision and no API call deletes one.
- Gotchas. Two rows: a branch name from a private project carries a private issue title; a
#123from a private project links to DocksDocks/docks#123.
Engineering standards
- Steps 1 and 2:
write-skill(body limits, the baseline check, and the trigger check) andcode-clarityfor the wording; step 1 alsofix-workflow(the experiment above is the reproduction). - Commits:
commit-discipline. Self-review during code review round 1:code-reviewwithcode-clarity.
Steps
| # | Id | Task | Files | Depends | Effect | Status | Done when |
|---|---|---|---|---|---|---|---|
| 1 | title_from_file | Implement decision 1 | plugins/docks/skills/productivity/report-skill-issue/SKILL.md | - | local | done | the stage 6 command and both link scripts read the title from a file, and the fake-gh experiment passes a title with backticks and $ unchanged |
| 2 | visibility_check | Implement decisions 2 to 5, then backfill the content hash | plugins/docks/skills/productivity/report-skill-issue/SKILL.md | 1 | local | done | the skill guard and scorer pass, the body stays at or under 310 lines, and the baseline check (a5) passes |
| 3 | release_docks | After the merge and only with its own question: run bun scripts/release.mjs --plugin docks patch from a clean main |
plugins/docks/.claude-plugin/plugin.json, plugins/docks/.codex-plugin/plugin.json, .claude-plugin/marketplace.json | 2 | release | done | the docks release tag CI passes and gh release view shows the release, or the step is skipped with the user's answer recorded |
Acceptance
| ID | Command | Expected |
|---|---|---|
| a1 | bun scripts/ci.mjs --plugin docks |
exits 0 (root AGENTS.md: the gate for a change owned by one plugin) |
| a2 | node plugins/docks/skills/productivity/write-skill/scripts/skill-guard.mjs score --per-file plugins/docks/skills |
productivity/report-skill-issue scores at or above its value before the change (15) |
| a3 | the fake-gh experiment from Research, run with the new stage 6 command and with each link script | the title with backticks and $ arrives unchanged |
| a4 | wc -l plugins/docks/skills/productivity/report-skill-issue/SKILL.md |
at most 310 body lines (plugins/docks/skills/AGENTS.md, body sweet spot) |
| a5 | Baseline check from write-skill: three fresh subagents each get one report scenario from a private project (repository name, branch name, issue numbers, and a second private repository in the error text; the gh visibility answers are given) and the skill text; once with the skill before this plan and once after |
after the change, no draft contains a private owner, repository name, branch name, private title, or #-number of the private project; the run before the change is recorded as the baseline |
| a6 | pull request checks | every check passes |
Do not touch
- The skill's description and its trigger behavior.
- The Report section at the end of the skill.
- Files outside
plugins/docks/skills/productivity/report-skill-issue/before the release step.
Open questions
Verification Results
Commits on the linked branch: 96922b83 (steps 1 and 2; both change the same sections of one file) and fbf42450 (a refinement from the a5 check).
- a1:
bun scripts/ci.mjs --plugin dockspassed at96922b83. - a2:
productivity/report-skill-issuescores 15, as before. - a3: with a fake
ghthat prints its arguments, the new stage 6 command passed the titledocks:report-skill-issue: \show` and $HOME and "quotes" in a title` unchanged; the Node and Python link scripts encoded the same title, and decoding gave it back unchanged. With the old command, a word in backticks vanished from the title. - a4: the file has 294 lines, at most 310.
- a5: three report scenarios from private projects (a private origin with a public upstream remote; a private origin with a second private repository in an error message; a public origin with a private fork remote). The drafts with the skill before this plan held no private owner, repository name, branch name, private title, or
#-number of the private project in any of the three runs, so these scenarios do not separate the two texts on leaks. The drafts with the new skill also held none; they applied the lookup rule (both remotes of a project with a private origin left out, numbers written without#, and the public project with a private fork remote treated as private), and they filed with the title read from a file. One draft also removed the#from a plan number inside quoted command output; commitfbf42450limits the rule to prose, and commit6eb23283keeps the#exemption to the number alone, so private names in quoted output still become placeholders. - a1 again:
bun scripts/ci.mjs --plugin dockspassed at6eb23283, the reviewed head.
Reviews (time from the first prompt to the final answer; tokens are input, cached input, cache writes, and output):
| Round | Mode | Reviewers (seconds; tokens M) | Verdict | Findings |
|---|---|---|---|---|
| Plan 1 | full | 116; 0.43 | repair | 1 security_risk: the argument-free lookup follows gh's defaults |
| Plan 2 | delta | 79; 0.39 | repair | the same class: a bare owner/name follows GH_HOST |
| Plan 3 | delta | 34; 0.15 | pass | none |
Code 1 at fbf42450 |
panel | correctness 61; 0.21 · spec-and-tests 68; 0.29 · text-and-safety 206; 0.35 | pass | 1 MEDIUM, 1 LOW |
| Code 1 | self-review, during round 1 | 224; 0.43 | - | 2 MEDIUM (the same two defects) |
Code 2 at 6eb23283 |
delta | 52; 0.15 | pass | none; both findings fixed |
Activity
DocksDocks commentedon Oct 23, 2026
Plan review round 1
Plan-review: repair
visibility_check- The argument-free visibility lookup need not inspect the user’s project: GitHub’s environment documentation saysGH_REPOoverrides local-repository selection. [INFERENCE] In a private checkout withGH_REPO=DocksDocks/docks, the proposed command returns the public reporting repository’s identity and visibility. The project can consequently miss the private-project redaction rule, allowing its branch names or issue titles into the draft instead of excluding them as Goal requires. The lookup-derived owner/name scan does not correct that mismatch. - Resolve the user project’s repository identity from its remote independently of gh’s ambient target, query that explicit repository, and bind the scan to that identity. Treat an unresolved or mismatched identity as non-public.DocksDocks commentedon Oct 23, 2026
Plan review round 2, delta
Plan-review: repair
visibility_check- The explicit owner/name still omits the remote’s hostname. CLIFromFullNamefills that fromauth.DefaultHost, which prioritizesGH_HOST. [INFERENCE] If that host contains aPUBLICrepository matching a private github.com repository’s owner/name, the lookup classifies the private project as public. Expected: exclude its private branch names and issue titles; actual: the draft may retain them. - Preserve the hostname from the resolved remote: querygh repo view github.com/<owner>/<repo> --json visibility --jq .visibility. Likewise bind other repository checks to their actual hosts, retaining fail-closed behavior.Previous findings:
visibility_check: open - repeated above. The repair removes default-remote ambiguity and binds the scan to remote-derived names, but the lookup’s hostname remains ambient.DocksDocks commentedon Oct 23, 2026
Plan review round 3, delta
Plan-review: pass
None.
Previous findings:
visibility_check: resolved - Decision 2 now requires explicitgithub.com/<owner>/<repo>lookups, including for other named repositories, and treats non-GitHub remotes, parse failures, and failed lookups as not public. This removes ambient-host selection while retaining fail-closed behavior.DocksDocks commentedon Oct 23, 2026
Code review round 1, lens correctness
Code-review: pass
Standards
None.
Spec
None.
Code review round 1, lens spec-and-tests
Code-review: pass
Standards
None.
Spec
None.
Code review round 1, lens text-and-safety
Code-review: pass
Standards
#123, these unconditional rules require rewriting it, contradicting stage 4’s explicit instruction at line 106 to preserve quoted output. Limit both rules to prose so the final checklist preserves exact evidence. - class: contradictory number-redaction scopeSpec
None.
DocksDocks commentedon Oct 23, 2026
Self-review
Ran while code review round 1 ran, on the full candidate diff at
fbf42450, with the dockscode-reviewandcode-clarityskills.#anywhere in the body, requiring altered evidence or preventing filing. - Apply the prohibition to prose and retain the code-span and fenced-output exception. - class: contradictory instructionsDocksDocks commentedon Oct 23, 2026
Code review round 2, delta
Code-review: pass
Previous findings
#only in prose. Line 106 limits the quoted-output exemption to numbers and explicitly requires replacing private owners, repository names, titles, and branch names with placeholders.Standards
None.
Spec
None.
fix(docks): check repository visibility in report-skill-issue
fix(docks): keep quoted output exact when numbers lose their #
fix(docks): scope the number rule to prose and name every revision de…