К основному содержанию
скилл CX Codex

copy-to-output-directory — скилл для Claude Code: режимы копирования

Скилл помогает выбрать режим CopyToOutputDirectory в MSBuild: Never, PreserveNewest, Always или IfDifferent. Пригодится, когда файлы в bin/ устаревают или сборка тормозит.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add dotnet/skills --skill copy-to-output-directory -a claude-code
Открыть на GitHub

Что делает copy-to-output-directory

Скилл разбирает четыре значения метаданных CopyToOutputDirectory и CopyToPublishDirectory. Never ничего не копирует, PreserveNewest копирует, только если исходник новее приёмника, Always копирует на каждой сборке, а IfDifferent — когда файлы различаются в любую сторону: новее, старее или другого размера.

Главная причина, по которой Always обычно лишний, — цена. Он перекопирует файл даже в no-op сборке, и на проектах с крупным контентом это заметно. Раньше Always брали, чтобы вернуть изменённый между сборками файл к исходному виду: базу SQLite, файл состояния, конфиг, который правит тест. IfDifferent решает ту же задачу дешевле. Проверка «не изменился» — эвристика по времени правки и размеру, без хеша содержимого, поэтому файл с теми же размером и меткой времени скопирован не будет.

Если в репозитории уже много Always, есть свойство $(SkipUnchangedFilesOnCopyAlways). Оно заставляет цель _CopyOutOfDateSourceItemsToOutputDirectoryAlways пропускать неизменённые файлы. По умолчанию свойство выключено ради обратной совместимости, и его можно задать в Directory.Build.props. И IfDifferent, и это свойство требуют MSBuild 17.13 или новее (.NET SDK 9.0.2xx+, Visual Studio 2022 17.13+). На старых тулсетах значение не распознаётся, и файл молча не копируется — это повод проверить версию SDK до того, как менять разметку проекта.

Какие задачи решает

  • убрать лишнее копирование файлов на каждой сборке
  • вернуть изменённый тестами файл к исходной версии
  • выбрать режим копирования для контента проекта
  • включить пропуск неизменённых файлов сразу для всего репозитория
  • понять, почему файл не попадает в bin/ на старом SDK

Примеры запросов агенту

  • Почему моя no-op сборка не мгновенная? Проверь CopyToOutputDirectory="Always" в проекте.
  • Тест правит fixtures/catalog.db, и после прогона он остаётся изменённым. Как сбрасывать его к исходнику на каждой сборке?
  • У нас в репозитории сотни Always. Можно ли включить пропуск неизменённых файлов через Directory.Build.props?

Как установить copy-to-output-directory

1Через Skills CLI (Vercel)

npx skills add dotnet/skills --skill copy-to-output-directory -a claude-code

2Вручную: положить файлы

~/.claude/skills/copy-to-output-directory/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/copy-to-output-directory/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Вопросы о copy-to-output-directory

Как установить copy-to-output-directory в Claude?

Выполните npx skills add dotnet/skills --skill copy-to-output-directory -a claude-code. Скилл появится в ~/.claude/skills/copy-to-output-directory/SKILL.md.

Что делает copy-to-output-directory?

Помогает выбрать режим CopyToOutputDirectory или CopyToPublishDirectory: Never, PreserveNewest, Always и IfDifferent. Объясняет, когда какой режим уместен и чем плох Always.

Какая версия MSBuild нужна для IfDifferent?

IfDifferent и $(SkipUnchangedFilesOnCopyAlways) работают на MSBuild 17.13 или новее, то есть .NET SDK 9.0.2xx+ и Visual Studio 2022 17.13+. На более старых тулсетах значение не распознаётся, и файл не копируется.

Исходный текст

SKILL.md · MIT
---
name: copy-to-output-directory
description: "Choosing an MSBuild CopyToOutputDirectory / CopyToPublishDirectory mode: Never, PreserveNewest, Always, and IfDifferent (MSBuild 17.13+), plus $(SkipUnchangedFilesOnCopyAlways). USE FOR: removing the per-build Always copy perf hit; resetting output files mutated between builds. DO NOT USE FOR: general incremental-build diagnosis (use incremental-build); non-MSBuild build systems."
license: MIT
---

# Choosing a CopyToOutputDirectory Mode

## Overview

The `CopyToOutputDirectory` metadata (and its publish counterpart `CopyToPublishDirectory`) controls whether an item — `Content`, `None`, `EmbeddedResource`, or `Compile` — is copied next to your build output, and under what conditions the copy happens. Picking the wrong mode causes either stale files in `bin/` or an unnecessary per-build performance hit.

As of **MSBuild 17.13 / .NET SDK 9.0.2xx** there are four values:

| Mode | Copies when… | Incremental cost | Typical use |
| --- | --- | --- | --- |
| `Never` (default) | Never | None | Files not needed at runtime |
| `PreserveNewest` | Source is **newer** than destination (or destination missing) | Cheap (timestamp check) | The common case — source files you edit |
| `Always` | **Every build**, unconditionally | Expensive — copies on every build even in no-op builds | Legacy workaround; avoid (see below) |
| `IfDifferent` | Source **differs** from destination in either direction (newer **or** older, or size differs, or destination missing) | Cheap (timestamp + size check) | Destination may be mutated between builds |

```xml
<ItemGroup>
  <None Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
  <None Include="testdata\seed.db"  CopyToOutputDirectory="IfDifferent" />
</ItemGroup>
```

You can use either the attribute form shown above or the child-element form:

```xml
<None Include="testdata\seed.db">
  <CopyToOutputDirectory>IfDifferent</CopyToOutputDirectory>
</None>
```

## Why `Always` is usually the wrong choice

`Always` re-copies the file on **every** build, including otherwise-clean incremental/no-op builds. On projects with many or large content files this is a measurable, recurring cost and a common cause of "why is my no-op build not instant?" reports.

Historically `Always` was the only way to handle a specific scenario: **the destination file can change between builds** — for example an SQLite database, a storage/state file, or a config file that a test run mutates. With `PreserveNewest`, if the destination is modified (making its timestamp *newer* than the source) MSBuild will *not* restore the original source file, because the source is no longer newer. People reached for `Always` to force the file back into a known-good state — paying the copy cost on every build as a side effect.

## `IfDifferent`: copy when different, in either direction

`IfDifferent` is the targeted fix for that scenario. It copies the source over the destination whenever MSBuild considers the two **different** — whether the source is newer *or* older than the destination, whether the size differs, or the destination is missing — and skips the copy when the destination is unchanged per MSBuild's heuristic.

Under the hood the `_CopyDifferingSourceItemsToOutputDirectory` target uses the `Copy` task with `SkipUnchangedFiles="true"`. That "unchanged" check is a **heuristic**: it compares **last-write timestamp and file size only** — not a content hash — so a destination that was edited to the same size and timestamp as the source is treated as unchanged and is *not* re-copied. In practice this restores a mutated destination back to the source version on the next build (the reason people reached for `Always`) while avoiding the unconditional per-build copy.

Use `IfDifferent` when:

- A test run or the app itself writes to the copied file (databases, caches, state/storage files, editable config) and you want each build to reset it to the source version.
- You were using `Always` purely as a "keep the output in sync with the source" mechanism, not because you truly need a copy on every single build.

```xml
<ItemGroup>
  <!-- Reset the fixture DB to the source copy whenever it has drifted,
       but don't pay a copy on every no-op build. -->
  <None Include="fixtures\catalog.db" CopyToOutputDirectory="IfDifferent" />
</ItemGroup>
```

## Globally softening `Always` with `$(SkipUnchangedFilesOnCopyAlways)`

If you have an existing codebase full of `CopyToOutputDirectory="Always"` items and want the performance benefit without editing every item, set the property:

```xml
<PropertyGroup>
  <SkipUnchangedFilesOnCopyAlways>true</SkipUnchangedFilesOnCopyAlways>
</PropertyGroup>
```

This makes the `_CopyOutOfDateSourceItemsToOutputDirectoryAlways` target pass `SkipUnchangedFiles="true"` to its `Copy` task, so `Always` items are only copied when they actually differ — effectively giving `Always` the same skip-unchanged behavior as `IfDifferent`.

- Default is `false` for backwards compatibility (classic `Always` = copy every build).
- Set it in `Directory.Build.props` to opt an entire repo in at once.
- Prefer converting individual items to `IfDifferent` when you can; use this property when a bulk, non-invasive opt-in is more practical.

## How the modes flow through the build

`GetCopyToOutputDirectoryItems` buckets each item by its `CopyToOutputDirectory` value. Three copy targets then do the work as dependencies of `_CopySourceItemsToOutputDirectory` (which is itself invoked by `CopyFilesToOutputDirectory`):

- `_CopyOutOfDateSourceItemsToOutputDirectory` — `PreserveNewest` items (incremental via `Inputs`/`Outputs` timestamp comparison).
- `_CopyOutOfDateSourceItemsToOutputDirectoryAlways` — `Always` items (unconditional copy unless `$(SkipUnchangedFilesOnCopyAlways)` is `true`).
- `_CopyDifferingSourceItemsToOutputDirectory` — `IfDifferent` items (`SkipUnchangedFiles="true"`).

All copied files are registered in `FileWrites`, so `dotnet clean` removes them.

**Transitive copy:** items marked `Always`, `PreserveNewest`, or `IfDifferent` also flow to referencing projects through `ProjectReference` (via `_CopyToOutputDirectoryTransitiveItems`). `Never` items do not. `IfDifferent` participates in ClickOnce publish item collection alongside `Always`/`PreserveNewest`.

## Version requirement

`IfDifferent` and `$(SkipUnchangedFilesOnCopyAlways)` require **MSBuild 17.13 or later** (**.NET SDK 9.0.2xx+ / Visual Studio 2022 17.13+**). On older toolsets the value is not recognized: it will not match the `Always`/`PreserveNewest`/`IfDifferent` conditions in the common targets, so the item is silently **not copied**. Gate usage on the toolset if you must support older SDKs, or require the minimum SDK via `global.json`.

## Quick decision guide

- Don't need the file at runtime → `Never` (or omit — it's the default).
- Normal source file you edit → `PreserveNewest`.
- Destination gets mutated between builds and must be reset to the source → `IfDifferent`.
- You truly need a fresh copy on literally every build → `Always` (rare).
- Stuck with lots of legacy `Always` and want the perf win without edits → keep `Always` but set `$(SkipUnchangedFilesOnCopyAlways)=true`.

© dotnet · MIT · dotnet/skills

Похожие скиллы

Всё по теме «DevOps и инфраструктура» →
tailing-build-output spencerpauly Следит за выводом сборки проекта в реальном времени, выявляет ошибки компиляции и правит код прямо в процессе работы Webpack, Turbo или Docker. CX Codex +1 384установок project-structure codewithmukesh Скилл задаёт агенту правила раскладки .NET-решения: центральные версии пакетов, общие свойства сборки, формат .slnx и именование проектов. CX Codex +1 2 800установок build-fix codewithmukesh Запускает автономный цикл починки сборки и тестов в проектах на .NET. Скилл разбирает ошибки компилятора, исправляет код и останавливается при регрессиях. CX Codex +1 2 800установок dotnet-project-structure Aaronontheweb Скилл подсказывает агенту, как разложить .NET-решение: .slnx, Directory.Build.props, центральные версии пакетов и SourceLink. CX Codex +1 658установок bazel-build-optimization Seth Hobson Скилл помогает настраивать Bazel для монорепозиториев, удалённого выполнения и кэширования. Подойдёт для разбора проблем сборки и ускорения пайплайна. CX Codex 10 300установок full-output-enforcement nexu-io Скилл просит агента выдавать полную реализацию без пропусков и заглушек. Пригодится, когда нужны все файлы или компоненты, а не набросок. CX Codex +1 1 600установок

Из того же набора101

target-authoring Скилл для написания кастомных MSBuild-таргетов: подсказывает канонические паттерны и ловит анти-паттерны вроде перезаписи цепочек DependsOn. CX Codex +1 3 000установок template-comparison Скилл сравнивает два и больше шаблонов dotnet new в таблице и подсказывает, какой брать под вашу задачу. Пригодится, когда выбираете между webapi и webapp или blazor и blazorwasm. CX Codex +1 2 800установок template-smart-defaults Скилл подсказывает агенту, какие связанные параметры подставить в dotnet new, когда часть флагов не задана, и не трогает то, что выбрал пользователь. CX Codex +1 2 800установок migrate-xunit-to-mstest Переводит тестовые проекты .NET с xUnit v2 или v3 на MSTest v4 с сохранением запуска через VSTest или MTP. Пригодится при стандартизации тестов в репозитории. CX Codex +1 2 900установок create-datadriven-aspnetcore Генерирует CRUD-код для ASP.NET Core без CLI-генераторов. Создаёт Razor Pages, Blazor-компоненты, контроллеры или Minimal API под стиль проекта и схему EF Core. CX Codex +1 2 800установок scaffold-dotnet-test-project Создаёт или чинит тестовый проект .NET: находит, почему CI не видит тесты, и прописывает проект в нужный solution-файл. CX Codex +1 2 600установок testability-obstacle Вводит минимальный шов в продакшен-код C#/.NET, чтобы тест перестал зависеть от реального времени, файлов и случайности. CX Codex +1 2 500установок vectorization Помогает проектировать, внедрять и проверять SIMD-код на платформе .NET. Нужен для ускорения скалярных циклов и поиска ошибок в векторных операциях. CX Codex +1 2 000установок

Спонсоры проекта

Компании, которые помогают держать аналитику открытой