Unstick a TFVC project from the deprecated check-in policy error that blocks Visual Studio check-ins after Azure DevOps Sprint 254/266.
It's not possible to operate with deprecated policies, please see Azure DevOps blog for more information. https://aka.ms/tfvc-policy-blogpost
If you're hitting that error in Visual Studio 2022 against a TFVC project, the Source Control Settings → Check-in Policy tab is empty, and adding any non-Obsolete policy doesn't persist — this tool fixes it.
D365 F&O developers are the largest group hitting this, because D365 F&O still requires TFVC. Jump to docs/D365FO-USERS.md for a 30-second quick start tailored for that environment.
After Sprint 266 (April 2026) Microsoft disabled the save path for the obsolete TFVC check-in policy format. Projects that still have an obsolete-format policy persisted server-side end up in a state where:
- Visual Studio refuses every check-in with the "not possible to operate with deprecated policies" error.
- The Source Control Settings → Check-in Policy tab shows nothing.
- Adding a new (non-Obsolete) policy via the UI does not save and does not clear the error.
Microsoft's official migration guide points at a MigrateFromOldPolicies method and references a "Remove existing obsolete policies" section — but that section does not exist in the published article (verified 2026-06-02), and calling MigrateFromOldPolicies directly trips the same server-side validation that's blocking everything else.
The reliable unstick is to call TeamProject.SetCheckinPolicies(null) directly via the Microsoft.TeamFoundationServer.ExtendedClient SDK. The server's deprecated-policy validation runs on reads but not on the null-write path, so the write succeeds and the obsolete entry is cleared. That's all this tool does.
See docs/BACKGROUND.md for the full technical writeup.
Prerequisites
- Windows with Visual Studio 2022 installed (or any environment with the .NET Framework 4.8 SDK + .NET SDK 6.0/8.0).
- You are a Project Administrator on the affected TFVC project (the Edit project-level information permission).
- The Azure DevOps host trusts your current Windows / AAD sign-in (true on most domain-joined dev machines, including all D365 F&O dev VMs).
Build
git clone https://github.com/marxhein94adaptable/tfvc-policy-migrator.git
cd tfvc-policy-migrator
dotnet build src/TfvcPolicyMigrator/TfvcPolicyMigrator.csproj
NuGet will restore Microsoft.TeamFoundationServer.ExtendedClient on first build.
Dry run (read-only — does not change anything on the server):
dotnet run --project src/TfvcPolicyMigrator -- ^
--integrated --dry-run ^
--org https://dev.azure.com/<your-org> ^
--project "<your-tfvc-project>"
Expected output: the "Legacy (obsolete) policies" line will either list one or more entries (confirming the stuck state) or print ERROR VersionControlException: It's not possible to operate with deprecated policies... (the same error you see in Visual Studio — also confirms the stuck state).
Apply the fix:
dotnet run --project src/TfvcPolicyMigrator -- ^
--integrated --clear-legacy ^
--org https://dev.azure.com/<your-org> ^
--project "<your-tfvc-project>"
Confirm y at the prompt. After the write, the AFTER section should show (none) for both new-format and legacy policies — no errors.
Verify in Visual Studio
- Close VS 2022.
- Reopen, open Team Explorer.
- Settings → Team Project → Source Control → Check-in Policy — the deprecated-policy banner should be gone.
- Try a small check-in. No
/overrideneeded. - Optionally re-add the policies you want (Work Items, Changeset Comments, etc.) via Add… with the non-Obsolete variants.
| Flag | Purpose |
|---|---|
--org <uri> |
Azure DevOps collection URI (env: TFVC_ORG_URL). Example: https://dev.azure.com/<your-org>. |
--project <name> |
TFVC project name (env: TFVC_PROJECT). |
--pat <token> |
Personal Access Token (env: TFVC_PAT). Ignored if --integrated is set. |
--integrated |
Use default Windows/AAD/VS-cached credentials. Required on AAD-backed orgs where PAT is rejected by the legacy SOAP endpoint with TF30063. |
--dry-run |
Print the BEFORE state and exit. No writes. |
--clear-legacy |
Call SetCheckinPolicies(null) on the project. This is the proven-working unstick path. |
--wipe-all |
Last-resort: call SetCheckinClientPolicies([]). Wipes every policy (new and old). |
--discover |
Offline scan of loaded TFS assemblies for any method with Polic or Migrate in its name. Useful when a future package release moves the methods this tool depends on. |
--help, -h, -? |
Print help and exit. |
Values can also be supplied via a .env file in the working directory or any ancestor — the tool walks up from the EXE folder until it finds one. See .env.example.
On Azure DevOps Services orgs that are AAD-backed, Conditional Access policies typically reject non-interactive authentication against the legacy LocationWebService SOAP endpoint. The same PAT that works against modern REST endpoints (e.g. /_apis/projects?api-version=7.1) returns 401 Unauthorized against the SOAP endpoint, surfacing as TF30063: You are not authorized to access ....
The legacy SOAP endpoint is the only auth gate to a TeamProject instance — which is the only object that exposes SetCheckinPolicies. So PAT does not work end-to-end for this operation on AAD-backed orgs.
--integrated constructs the TfsTeamProjectCollection with no explicit credentials, which lets the SDK fall back to default Windows / AAD / Visual Studio cached credentials. Those flow through Conditional Access cleanly on a domain-joined or AAD-joined machine.
Full background: docs/BACKGROUND.md.
| Environment | Status |
|---|---|
| Azure DevOps Services (cloud) — AAD-backed orgs | ✅ Confirmed working with --integrated --clear-legacy. |
| Azure DevOps Services (cloud) — MSA-backed orgs | ❓ Untested. PAT-based flow may work; please open an issue with results. |
| Azure DevOps Server (on-prem) | ❓ Untested. The package targets the same SDK so it should work; open an issue if you confirm. |
| Custom check-in policies | ❌ Out of scope. This tool is for the stuck obsolete-format storage case only. Custom policy migration needs a different approach — see Microsoft's migration guide. |
See docs/TROUBLESHOOTING.md for TF30063, TF14045, MissingMethodException, and "migration succeeded but obsolete entries persist."
Microsoft phased out the legacy TFVC check-in policy storage across two sprints:
- Sprint 254 (May 2025) — the
Microsoft.TeamFoundationServer.ExtendedClientNuGet was repointed at the new policy storage model. - Sprint 266 (April 2026) — the save path for obsolete policies was disabled.
For projects that still had an obsolete-format policy in storage when the Sprint 266 change rolled out, Visual Studio's UI broke. Microsoft's migration guide references a #remove-existing-obsolete-policies section — but that section does not exist in the published article, and the documented MigrateFromOldPolicies method is server-side-blocked by the very condition it's meant to bypass.
This tool exists to close that gap. It's the smallest possible direct call to the SDK method that actually clears the storage.
Bug reports especially welcome — every report helps confirm which Azure DevOps configurations hit this and which don't. Use the Stuck Policy Report issue template and paste in the BEFORE / AFTER output from a --dry-run. See CONTRIBUTING.md.
MIT — see LICENSE.