Skip to content

fix(ci): fail in one second on duplicate sources, not twenty minutes into an archive - #430

Merged
JasonYeYuhe merged 1 commit into
mainfrom
guard-duplicate-sources
Aug 11, 2026
Merged

fix(ci): fail in one second on duplicate sources, not twenty minutes into an archive#430
JasonYeYuhe merged 1 commit into
mainfrom
guard-duplicate-sources

Conversation

@JasonYeYuhe

Copy link
Copy Markdown
Collaborator

The iOS App Store archive died today after ~20 minutes:

'WatchSessionIdentity' is ambiguous for type lookup in this context
  found this candidate: WatchSessionBoundary 2.swift
  found this candidate: WatchSessionBoundary 3.swift
  found this candidate: WatchSessionBoundary.swift

25 such files under CLIPulseCore/Sources, every one byte-identical to its original, dated 07-30 to 08-03.

Why .gitignore didn't save us

It already matches * [2-9].* — and that is exactly the point. Ignoring a file protects the index, not the build. SwiftPM globs Sources/ and Tests/, so unlike the Xcode app target (which compiles only what project.pbxproj lists), every stray copy gets compiled.

They were invisible to git status, invisible to review, and fatal to xcodebuild archive — surfacing as a type-ambiguity error that reads like a code bug rather than a stray file, at the end of a long archive. Worst possible failure mode.

The guard

Distinguishes two cases, because they need opposite responses:

report meaning action
byte-identical duplicate of X stray copy delete
duplicate NAME of X, but CONTENT DIFFERS divergent work inspect first

Verified both directions — clean tree passes; recreating one of each makes it fail and classify them correctly.

Runs in Repo Hygiene, which has no path filter, so it also covers the docs-only and migration-only PRs that swift-ci skips.

On the cause — honestly, unknown

Long assumed to be iCloud. That's probably wrong:

  • ~/Documents is a plain directory; Desktop & Documents sync is off
  • zero .icloud placeholders anywhere in the repo
  • OneDrive syncs only ~/OneDrive - keio.jp

Neither candidate covers this path. Since the source is unidentified, deleting them is a reset, not a fix — they have already come back once (14 deleted 08-07, 25 present 08-11). That's the argument for a guard rather than another cleanup.

🤖 Generated with Claude Code

…into an archive

The iOS App Store archive failed on 2026-08-11 after ~20 minutes with:

    'WatchSessionIdentity' is ambiguous for type lookup in this context
      found this candidate: WatchSessionBoundary 2.swift
      found this candidate: WatchSessionBoundary 3.swift
      found this candidate: WatchSessionBoundary.swift

There were 25 such files under CLIPulseCore/Sources, every one byte-identical to
its original, dated 07-30 to 08-03. SwiftPM globs its Sources directory, so
unlike the Xcode app target — which compiles only what project.pbxproj lists —
every stray copy is compiled.

.gitignore already matches `* [2-9].*`, and that is precisely why this needed a
separate guard: **ignoring a file protects the index, not the build.** They were
invisible to `git status`, invisible to review, and fatal to `xcodebuild
archive`. The error also reads like a code bug rather than a stray file, which
is the worst possible failure mode at the end of a long archive.

The guard distinguishes byte-identical strays (delete them) from same-name
files whose CONTENT DIFFERS (inspect — that is divergent work, not a copy).
Verified both directions: clean tree passes; recreating one of each makes it
fail and report them correctly.

Runs in Repo Hygiene, which has no path filter, so it also covers the docs-only
and migration-only PRs that swift-ci skips.

ON THE CAUSE, HONESTLY: unknown. Long assumed to be iCloud, and that is
probably wrong — `~/Documents` is a plain directory with Desktop & Documents
sync OFF, the repo holds zero `.icloud` placeholders, and OneDrive syncs only
`~/OneDrive - keio.jp`. Since the source is unidentified, deleting them is a
reset rather than a fix; they have already returned once (14 deleted 08-07, 25
present 08-11). Hence a guard rather than a cleanup.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 10, 2026 18:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a CI hygiene guard to fail fast when duplicate-named source files (e.g., Foo 2.swift) appear in SwiftPM-compiled directories, preventing long xcodebuild archive runs from failing late with “ambiguous for type lookup” errors.

Changes:

  • Introduce scripts/check_no_duplicate_sources.sh to detect and classify duplicate-style files in SwiftPM Sources/ and Tests/ directories.
  • Run the new guard as part of the Repo Hygiene GitHub Actions workflow to ensure it executes even for PRs that don’t trigger Swift CI.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
scripts/check_no_duplicate_sources.sh New repo hygiene script to detect duplicate-named compiled sources and fail CI early.
.github/workflows/repo-hygiene.yml Wires the duplicate-source guard into the existing Repo Hygiene workflow.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +57 to +70
while IFS= read -r -d '' f; do
base="$(basename "$f")"
canonical_base="$(sed -E 's/ [0-9]+(\.[A-Za-z0-9]+)$/\1/' <<< "$base")"
canonical="$(dirname "$f")/$canonical_base"
if [[ -f "$canonical" ]]; then
if cmp -s "$f" "$canonical"; then
offenders+="${f} — byte-identical duplicate of ${canonical_base}"$'\n'
else
offenders+="${f} — duplicate NAME of ${canonical_base}, but CONTENT DIFFERS (inspect before deleting)"$'\n'
fi
else
offenders+="${f} — duplicate-style name with no original present"$'\n'
fi
done < <(find "$dir" -type f -name "* [0-9].*" -print0 2>/dev/null)
@JasonYeYuhe
JasonYeYuhe merged commit f7a4140 into main Aug 11, 2026
4 checks passed
@JasonYeYuhe
JasonYeYuhe deleted the guard-duplicate-sources branch August 11, 2026 09:30
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