Skip to content

[DEVT-3120] feat(push): redeploy only services whose files changed - #278

Draft
jin-joung wants to merge 1 commit into
GoodRxOSS:mainfrom
jin-joung:feat/per-service-push-skip
Draft

jin-joung wants to merge 1 commit into
GoodRxOSS:mainfrom
jin-joung:feat/per-service-push-skip

Conversation

@jin-joung

@jin-joung jin-joung commented Oct 29, 2026 •

Copy link
Copy Markdown

feat(push): redeploy only services whose files changed

https://goodrx-dev.atlassian.net/browse/DEVT-3120

Problem

shouldSkipPushDeploy is all-or-nothing per build: the push is skipped only if every changed file is ignored
by every affected service. In a monorepo with several services from one repo, a change that one service ignores
still rebuilds all of them. Image tags are lfc-<shortSha>-<envVarsHash>, so every service rebuilds on every
commit. In one production monorepo only about 5% of pushes skip, and about 37% of app image builds are for
services whose dependency closure did not change.

Change

  • Per-service decision. shouldSkipPushDeploy evaluates each service policy separately and returns
    skipServices when some, but not all, services ignore every changed file. Full-skip behaviour and reasons are
    unchanged.
  • Intent carries the skip set. enqueueResolveAndDeployBuild accepts skipServices and stores it on
    repository-scoped source intents only (target: 'all' intents ignore it).
  • Stamping leaves skipped rows alone. stampDeploysForIntent and adoptDeploysForIntent exclude a row only
    when its service is in skipServices, it is settled (observedGeneration >= desiredGeneration), and its status
    is READY, DEPLOYED or BUILT. Pending rows (including rows a replaced intent stamped), failed rows and
    never-deployed rows are always stamped, so coalesced pushes cannot strand work. The fenced pass already builds
    and applies manifests only for rows at the run's generation, so skipped services keep their image and release.
  • includeFiles (watch paths). New service-level option. A file is ignored for a service if it matches
    ignoreFiles or falls outside a non-empty includeFiles. Glob ignore lists cannot express "everything except
    my dependency closure"; an include list can. Cap is 200 patterns (ignoreFiles keeps 50). Glob matchers are now
    compiled once per policy instead of once per file.
  • Rollout flag. Per-service skipping requires both features.ignoreFiles and a new
    features.ignoreFilesPerService. With the new flag off, Lifecycle logs
    Push: dry-run would skip services reason=ignoreFiles with the service list and redeploys everything.

Preserved

  • A lifecycle.yaml / .lifecycle.yml change never skips.
  • Failed deploys on PR environments still force a full redeploy (no skip set on that path).
  • Static and config-repo pushes still bypass ignore checks.
  • Webhooks still queue for the new SHA (full skip path unchanged; partial skip runs a normal pass).
  • The ignoreFiles feature flag gate.

Known limitation

findOrCreateDeploys still advances deploys.sha for skipped rows in the pushed repository and branch, so the
UI and status comment show the new SHA for a service that is still running the previous image. Under the
service's own policy the content is equivalent. Keeping the old SHA would mean passing the skip set into
findOrCreateDeploys; happy to do that here or in a follow-up.

Tests

  • src/server/lib/__tests__/pushIgnoreFiles.test.ts: partial skip list, missing policy plus skip, includeFiles
    semantics, includeFiles-only policy, includeFiles validation and cap, config change with includeFiles.
  • src/server/lib/deploymentReconciliation/__tests__/mailbox.test.ts: skip filter applied to the stamp;
    not applied for an empty list or target: 'all'.
  • src/server/services/__tests__/build.test.ts: skipServices kept on repository-scoped push intents only.
  • src/server/services/__tests__/github.test.ts: partial skip enqueues skipServices with both flags on;
    dry-run with either flag off.

Results: the 8 related suites (push ignore, mailbox, build, github, github.behavior, githubAutoTrack,
YamlService, yamlSchemas) pass with .env.ci loaded. Full jest run: 10,053 passed, 1 failed; the failure is
api-keys/route.test.ts reading OPENAI_API_KEY from the local shell and passes with it unset. eslint is clean
on changed files. pnpm run generate:schemas reproduces the JSON schema and YAML docs byte for byte.
ts-check (disabled in CI) reports no new errors on changed source lines.

Rollout

  1. Merge with ignoreFilesPerService unset and watch the dry-run log for wrong skips.
  2. Turn on ignoreFilesPerService.

A push now redeploys only the services whose own policy does not ignore
every changed file. Skipped services keep their current image; settled,
healthy rows of those services are left out of the source intent's
stamp. Pending, failed, or never-deployed rows are always stamped, so a
replaced intent's work is carried forward.

Adds a service-level includeFiles (watch paths) option and gates the
per-service skip behind a new ignoreFilesPerService flag (dry-run logs
when off).
@jin-joung jin-joung changed the title feat(push): redeploy only services whose files changed [DEVT-3120] feat(push): redeploy only services whose files changed Oct 29, 2026
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.

1 participant