Implementation guide
How to run only affected tests safely
Affected-test selection can shorten feedback and reduce compute, but only when missing dependency evidence causes a full run—not a smaller, unjustified selection.
Three common selection strategies
Changed paths
Path filters map directories or file patterns to commands. They are transparent, cheap, and often the best first baseline. They become difficult to maintain when shared packages, generated code, configuration, or cross-package imports make the mapping many-to-many.
Task or package graphs
Monorepo tools can calculate affected projects from their declared task graph. This is efficient when the repository already encodes project boundaries and task dependencies. The graph is only as complete as that configuration.
Source dependency graphs
A source analyzer follows imports from changed files toward tests. It can distinguish changes inside the same package more precisely, but resolution rules, dynamic imports, framework transforms, and unsupported languages create explicit boundaries.
The safest evaluation compares all three where possible: the existing full command, a simple path baseline, and the more precise selection.
Worked TypeScript example
Suppose a change touches src/tax.ts. A directory rule may run every test under tests/; a package graph may run the whole billing package; a source graph can follow the actual import chain:
src/tax.ts
├─ src/invoice.ts ── tests/invoice.test.ts
└─ src/refund.ts ── tests/refund.test.ts
unrelated:
src/profile.ts ── tests/profile.test.tsThe two billing tests are candidates only if the analyzer also resolved the repository's aliases, test discovery, shared setup, and runner command. If tests/setup.ts imports a changed global fixture—or if the test runner loads files dynamically—the correct answer may still be the full suite.
Evidence the result should retain
- Git identity: the exact base and head commits used for the comparison.
- Selection provenance: why each test is reachable from the changed source.
- Command plan: the runner, arguments, working directory, and required environment.
- Fallback reason: the unresolved edge or global input that prevented a safe narrow run.
Define full-suite triggers first
Fallback policy is part of the selector, not an error handler added later. Name the changes that invalidate narrow selection before measuring savings.
- Dependency manifests and lockfiles: resolved packages can change across the repository.
- Workflow and runner configuration: execution behavior can change without a source import.
- Root compiler or test configuration: transforms, aliases, environments, and discovery rules can change globally.
- Unsupported or unresolved imports: an incomplete graph cannot establish a complete affected set.
- Missing Git history: shallow checkouts may not contain the comparison base.
Maintain an explicit always-run list for security checks, policy tests, configuration validation, and other repository-specific guardrails.
Preserve execution prerequisites
A selected test command may still need installation, code generation, build output, services, environment variables, or artifacts. A command that passes only because a developer's checkout already contains generated state is not a reproducible CI selection.
Record the command plan as well as test files. If prerequisites cannot be represented, label the selection advisory or run the full path.
Verify safety and economics separately
Safety asks whether the selected path detects the failures it should. Economics asks whether analysis plus selected execution is cheaper than the baseline. A selector can be safe and still save nothing.
- Replay known changes
Include ordinary green changes and changes with known relevant failures.
- Execute the proposed command
Do not infer success from selection counts or command construction alone.
- Confirm the selection was honored
Test-runner output must match the intended set; otherwise withhold the timing.
- Compare job-equivalent cost
Include analysis, installation, setup, and test startup where the adoption decision includes them.
Troubleshoot the first observation
| Result | Likely cause | Next action |
|---|---|---|
REFUSED | Missing base commit, unsupported repository shape, or incomplete configuration. | Preserve the diagnostic, fetch the comparison history if appropriate, and run normal validation. |
| Full fallback | A global file changed or dependency evidence could not be completed safely. | Treat the full run as the correct selected plan; do not convert it into an empty set. |
| Selected command runs extra tests | The runner ignored or broadened file arguments. | Mark the selection unhonored and withhold its timing. |
| Subset fails only after setup | Generated output, services, environment, or artifacts were omitted. | Add the missing prerequisite to the execution plan or keep the result advisory. |
For ecosystem-specific limits, review the language and framework support matrix.
Try an observation before changing CI
npx "@diffci.com/diffci@latest" observe --no-sendThis produces affected-test and fallback evidence without running tests. Use check when paired full and selected execution is permitted:
npx "@diffci.com/diffci@latest" checkNeither command changes which tests required CI runs. Continue with the GitHub Actions guide or compare selection strategies.