---
title: "The note said Built. It had never been deployed."
description: "Five deploy errors, one cause. A foundational custom field and its global value set were written, committed, and never deployed — and every later piece of work assumed they existed. A dry-run found in seconds what reading the source never could."
source: "https://noconaconsulting.com/blog/built-does-not-mean-deployed/"
---

# The note said Built. It had never been deployed.

_Build log · Doorvana · Garage door manufacturing & service_

Five deploy errors, one cause. A foundational custom field and its global value set were written, committed, and never deployed — and every later piece of work assumed they existed. A dry-run found in seconds what reading the source never could.

Nocona Consulting · October 6, 2026 · 5 min read

Somebody opens the project notes to check whether a thing exists before building on it. There is a heading that says **Built:**, and the thing is listed under it. Good. Carry on.

It had never been deployed. It had been written months earlier, committed, reviewed, and then left in a branch that reached no org. Five separate deployment errors later, all of them tracing back to that one fact, is how it was found.

## The dependency

The field is `Account.Customer_Type__c` — Residential, Builder, Commercial, Dealer — plus the `GlobalValueSet` it is built on. It is about as foundational as a field gets on this org. Lead and Account both point at the same value set so that [lead conversion field mapping does not drift](https://noconaconsulting.com/blog/record-types-vs-picklists/). Validation rules key off it. The household naming logic branches on it. Half the automation written since reads it.

None of it was in the live org. The org had four custom fields on that object. The source expected five.

## Five errors, one cause

The deployment that surfaced it was not about the field. It was about the work sitting on top of the field, and it came back with five errors in five different places, none of which said “this field does not exist here.”

Each of those reads like its own problem. Debugged individually, each one has its own plausible local fix, and all five of those fixes are wrong.

Rule of thumb

Before fixing any error in a multi-error deployment, group them by what they reference. Five errors that all touch one component are one error. The temptation to start at the top of the list and work down is how a project ends up with five workarounds around a gap nobody closed.

The message that actually gave it away was the one about the value set.

sf project deploy start --dry-run

```plaintext
Error  Account.Customer_Type__c  The global picklist cannot be resolved.
```

That error is precise once you read it as a statement about the _org_ rather than about the source. The field references a `GlobalValueSet`. The org does not have one by that name. Not malformed, not mismatched — absent. And a `GlobalValueSet` that is absent from the org while being present in source has exactly one explanation.

## Why reading the code could never have found it

This is the part worth sitting with, because it generalises past this one project.

The source was correct. Every file was well-formed, the field definition was right, the value set was right, and the references between them were right. A reviewer reading the repository would have found nothing, because there was nothing wrong with the repository. The defect was not in the code; it was in the relationship between the code and an org, and that relationship is not visible from inside the code.

SFDX source format makes this easier to miss rather than harder. The repository is a complete and self-consistent description of a Salesforce org. It looks like the org. It is arranged like the org. What it is not is _evidence about_ any particular org, and nothing in the checkout distinguishes a component that has been deployed everywhere from one that has been deployed nowhere.

Correct source and an empty org look identical from the repository

Commit and deploy are separate actions with no enforced relationship between them. Work can be written, reviewed and merged without ever touching an org. Nothing in the repository records which org, if any, a given component has reached — so the question cannot be answered by reading, only by asking an org.

## The cheap thing that answers it

A validation-only deployment. Seconds, no side effects, authoritative.

verify the dependency before building on it

```bash
# does the org actually have the thing the plan says is built?
sf project deploy start \
  --source-dir force-app/main/default/objects/Account/fields/Customer_Type__c.field-meta.xml \
  --dry-run

# and what is actually on the object right now
sf sobject describe --sobject Account \
  | jq '[.fields[] | select(.custom) | .name]'
```

The describe is the one that settles arguments. Four names came back where the source expected five, and at that point there is nothing left to discuss — the conversation stops being about whether the work was done and becomes a two-minute deployment.

## The note got amended, which is the real output

It would have been easy to deploy the field, close the five errors, and move on. The more useful change was to the notes.

**“Built” now means “written down.”** That is what it had always meant on this project; it just had not been said out loud, and the heading implied something stronger than anyone had intended to claim. Deployment is a separate claim, about a named org, requiring separate evidence — a describe, a retrieve, a dry run, something that asked the org.

The vocabulary is doing real work here. _Written_, _committed_ and _merged_ are all claims about a repository and are all cheap to verify. _Deployed_ is a claim about an org and is the only one that matters when you are about to build on top of something. A project note that says a thing is built, without naming where, is ambiguous in the most expensive direction available, because the reader will resolve the ambiguity in the optimistic direction every time.

## What I do differently now

**Verify the dependency, not the plan.** When starting work whose first line assumes something exists, spend the ten seconds asking the org. The cost of the check is negligible and the cost of skipping it is a debugging session that begins in entirely the wrong place — reading your own new code for a mistake that is not in it.

**Keep the deploy loop short.** A component written months before it is deployed is a component with months of opportunity to become untrue. This gap existed because the two events were far apart, and nothing in the process noticed the distance.

**Prefer statements that name an org.** “Deployed to the sandbox on Tuesday, verified by describe” survives contact with a future reader. “Built” does not.

The failure mode here is not carelessness. Every individual step was done properly: the field was designed correctly, written correctly, and recorded honestly by someone who believed “built” meant what they meant by it. The gap was in a handoff that nothing in the tooling supervises, which is exactly the kind of gap that does not close by trying harder — only by changing what the words are allowed to mean.

-   SFDX
-   Source control
-   Deployment
-   Global value sets
-   Process

Disclosure: Doorvana's owner is also a co-owner of Nocona Consulting. This is a real engagement between two separate companies, but not an arm's-length one — and not an independent reference. [More on the work page](https://noconaconsulting.com/work/).

## FAQ

### What does The global picklist cannot be resolved mean on a Salesforce deploy?

It means the custom field being deployed references a GlobalValueSet that the target org does not have. The most common cause is that the value set exists in source but was never deployed, so the field arrives pointing at something that is not there. The fix is to deploy the GlobalValueSet first, or in the same deployment, since a field on a global value set cannot be created before the value set it depends on.

### How do you check whether Salesforce source matches the org before building on it?

Run a validation-only deployment of the dependency, such as sf project deploy start with --dry-run, and compare the org's actual component list against what the source expects. A dry run answers in seconds and is authoritative, because it asks the org rather than asking the repository. Reading the source cannot answer the question at all, since correct source and an empty org look identical from the repository side.

### Why do Salesforce source and the org drift apart?

Because committing and deploying are separate actions with no enforced relationship. Work can be written, reviewed and merged without ever reaching an org, and an org can be changed in Setup without anything reaching the repository. Drift in the first direction is the more dangerous one, because the repository looks complete and every later piece of work is built on a dependency that is only hypothetical.

### What is the right meaning of built on a Salesforce project?

Treat built as a claim about an org, not about a repository, and require separate evidence for it. Written, committed and merged are claims about source. Deployed is a claim about a specific org and is verifiable by retrieving or describing the component there. A project note that says built without naming the org it is built in is ambiguous in the most expensive possible direction.

### Should a failing deploy be debugged error by error?

Not at first. Multiple errors from one deployment frequently share a single root cause, particularly when they involve a common dependency such as a field, a value set or an object. Group the errors by what they reference before fixing any of them. Five errors that all touch the same field are one problem, and fixing them individually produces five workarounds around a gap that should simply be closed.
