Skip to content

Classify a fork by its parent's topics - #23

Merged
matt-edmondson merged 1 commit into
mainfrom
claude/dependabot-ci-workflow-rollout-fbrhdn
Sep 16, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
claude/dependabot-ci-workflow-rollout-fbrhdn

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

The problem

A fork does not inherit its parent's topics. ktsu-dev/winget-pkgs is the proof to hand — a fork of microsoft/winget-pkgs, where the parent carries topics and the fork's repository object has none at all.

detect required the dotnet topic, so as written it failed every fork of every ktsu repository, and failed it like this:

::error::This repository has a global.json but not the 'dotnet' topic.
::error::Add the 'dotnet' topic; the topic is what selects the pipeline.

A stranger who forks Sorting to fix a bug gets told to add organization metadata they do not own. Before shared CI a fork got a complete local dotnet.yml and simply worked, so this is a regression I introduced.

The fix

Borrow the parent's topics when the repository is a fork and has no dotnet topic of its own. A fork then classifies as whatever it was forked from.

The marker-file check still applies, and files are inherited, so nothing is loosened about what a fork has to actually be. A fork that sets its own dotnet topic is taken at its word and the parent is never consulted. A parent that cannot be read — deleted, or private to someone else — is not fatal on its own; it falls through to the existing checks, with a fork-specific message.

What I did not add, and why

I proposed also gating publish and Sonar on the owner. Checking rather than assuming turned up one guard already in place for each concern:

Concern Existing guard
Publishing ktsubuild is passed EXPECTED_OWNER: ktsu-dev and decides should_release itself
SonarQube every Sonar step is gated on env.SONAR_TOKEN != '', which is empty in a fork
Installing the build tool dotnet tool install ktsu.KtsuBuild.Tool from public NuGet, no secret

dotnet-private.yml additionally hard-codes should_release=false under a pre-release hold, and its commented-out computation already spells out the same idea — isOfficial = (not isFork) and isExpectedOwner.

A second owner check would have duplicated deliberate existing intent in a third place. So a forker gets discovery, build and tests across all three platforms, and the steps needing ktsu's secrets skip themselves.

Testing

The classify script was extracted from the YAML and run against a stubbed gh — ten cases, including the ones that only exist for forks:

Case Result
upstream, dotnet topic + global.json ✅ stack=dotnet
upstream, private ✅ stack=dotnet, private=true
fork, no topics, parent has dotnet ✅ inherits → stack=dotnet
fork with its own dotnet topic ✅ selected, parent never called
fork, parent unreadable ✅ fails with the fork-specific message
fork with no .parent field ✅ same
fork inherits topic but no global.json ✅ fails on the marker file
dotnet topic, no global.json (blogs, ktsu.dev) ✅ fails as before
no topic, no global.json ✅ fails as before

actionlint 1.7.7 with shellcheck clean; markdownlint-cli clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_014RABe2NufFc9hwm94iB3Rf


Generated by Claude Code

A fork does not inherit its parent's topics. ktsu-dev/winget-pkgs, a fork of
microsoft/winget-pkgs, is the proof to hand: the parent carries topics, the
fork's repository object has none at all.

detect required the dotnet topic, so as written it failed every fork of every
ktsu repository -- and failed it by telling a stranger to add organization
metadata they do not own. Before shared CI a fork got a complete local
dotnet.yml and simply worked, so this was a regression I introduced.

Borrow the parent's topics when the repository is a fork and has no dotnet
topic of its own. A fork then classifies as whatever it was forked from. The
marker-file check still applies and files are inherited, so nothing is loosened
about what a fork has to actually be; a fork that sets its own topic is taken at
its word and the parent is never consulted.

Nothing else needed a fork gate. Checking rather than assuming turned up one
already in place for each concern: ktsubuild is passed EXPECTED_OWNER and
decides should_release itself, every Sonar step is gated on SONAR_TOKEN being
non-empty, and the build tool installs from public NuGet without a secret. A
second owner check would have duplicated deliberate existing intent.

Ten cases were run against the extracted script with a stubbed gh: upstream
public and private, a fork inheriting its parent's topic, a fork with its own
topic (parent not consulted), a fork whose parent is unreadable, a fork with no
parent field, a fork inheriting a topic but lacking global.json, and the
topic-without-global.json case that blogs and ktsu.dev would hit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RABe2NufFc9hwm94iB3Rf
@matt-edmondson
matt-edmondson merged commit 1779e65 into main Sep 16, 2026
3 checks passed
@matt-edmondson
matt-edmondson deleted the claude/dependabot-ci-workflow-rollout-fbrhdn branch September 16, 2026 10:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants