The CocoaPods command reference distinguishes two dependency workflows: pod install and pod update do not serve the same purpose (CocoaPods command reference). For a CocoaPods install failure in remote Mac CI, first identify which Ruby, Bundler, and pod executable the job actually invokes. Then isolate Specs access, private-repository authentication, lockfile behavior, or source downloads. Preserve the original lockfile and reproduce the failure before changing dependencies or clearing caches.

This week, capture the failing command and environment from the runner, then rerun the same commit after one targeted change.

This runbook is for engineers maintaining GitHub Actions or other macOS CI pipelines who need to compare the runner with a working development machine.
It is also for iOS teams using private Pods and engineers who need to prove that a fix preserves the intended dependency versions.

01

Local success vs. runner failure

A local pass does not prove that the project configuration is correct in CI. The local shell may load a different Ruby manager, credentials, or shell profile. The runner may use a different account, start in another directory, or execute a non-interactive shell with a different PATH.

Start by locating the first failing phase, not the last error printed by the job. CocoaPods may fail before its command starts, while resolving a Pod, while fetching Specs or source code, or later when integrating dependencies with Xcode. Those failures require different evidence.

Compare the local and remote runs using the same commit and these details:

  • The full command line, including whether it uses bundle exec.
  • The execution account and current working directory.
  • The resolved paths and versions for Ruby, RubyGems, Bundler, and CocoaPods.
  • The first relevant error, with surrounding output and timestamps.
  • The Podfile and Podfile.lock used by the job.
  • The phase that failed: command startup, dependency resolution, download, project integration, or a later Xcode build.

A successful command on a laptop can also depend on credentials or repository access that the CI account does not have. Treat local success as a comparison point, not as proof that the runner has the same environment.

Evidence rule: Preserve the first error before retrying. A later retry may succeed after a transient network issue, but it does not explain why the original run failed.

02

Ruby and Bundler selection: interactive shell vs. CI shell

A common source of drift is assuming that the pod command in an interactive terminal is the same executable used by the CI job. Shell initialization can change PATH, and a system Ruby, a separately installed Ruby, and a project-managed bundle can resolve different gems.

Run these checks inside the failing job, from its actual working directory:

pwd
printf '%s\n' "$PATH"
which ruby
ruby -v
which gem
gem env
which bundle
bundle -v
which pod
pod --version

The output is evidence about that job, not a universal diagnosis. Compare it with the local output from the same project directory. If which pod points outside the bundle expected by the project, or the CI command omits bundle exec, the job may be loading a different CocoaPods installation.

When a repository includes a Gemfile, use it to describe the Ruby gem dependencies needed by the project. CocoaPods documents this approach in its Gemfile and Bundler guide. Confirm that the intended Gemfile and Gemfile.lock are present in the checkout and that the CI job installs and invokes the bundle from the correct directory.

A typical invocation looks like this:

bundle install
bundle exec pod install

Do not copy these commands blindly into every pipeline. If the project does not use Bundler, first establish which Ruby and CocoaPods installation it intends to use. If it does use Bundler, check that the job does not install gems under one Ruby and run pod under another.

CocoaPods’ installation guide documents the supported installation path. Use it to verify the intended setup, then compare that setup with the environment actually emitted by the runner. A version printed in a local terminal is not evidence about the CI process.

03

Specs and CDN access: resolution failure vs. network failure

When a Pod cannot be found or downloaded, separate source selection from network transport. A message about an unavailable Pod or version is not automatically a CDN outage. Likewise, a TLS or connection error does not by itself establish that the Podfile is wrong.

Read the earliest relevant error and classify it:

  • Source configuration: Check whether the Podfile declares the expected sources and whether a private source must take precedence. The Podfile syntax reference describes source declarations and related configuration.
  • Name or version resolution: Confirm the Pod name and requested version against the project’s declared sources. Check whether the source configuration used by CI matches the one used locally.
  • HTTP response: Record the URL, response status, and request time where the logs safely expose them. A server response is different evidence from a name that cannot be resolved.
  • TLS or connection failure: Check the runner’s DNS resolution, proxy settings, certificate trust, and outbound network access. Use the CI environment’s approved network diagnostics.
  • Source checkout: If Specs resolve but downloading the Pod source fails, investigate access to the source repository separately from Specs access.

Do not switch a source or declare a CDN incident after a single unsuccessful request. The task here is to collect reproducible evidence from the runner and check it against the Podfile and configured sources. CocoaPods’ troubleshooting guide is a reference for interpreting common failure classes, but a general guide cannot confirm the current availability of a particular endpoint.

Record which source was selected and where the request failed. Redact credentials, private repository details, and sensitive URL parameters before retaining or sharing logs.

04

Private Pods: configured source vs. authorized account

A private Pod can fail at more than one access boundary. The Specs repository may be missing or unreadable. The Podfile may select a different source than expected. Or the source repository for the Pod may reject the CI account even though the Specs entry was available.

Check these separately:

  • Confirm that the private Specs repository is configured in the runner’s CocoaPods environment.
  • Confirm that the Podfile selects the intended source for the private Pod.
  • Test read access to the Specs repository as the account that runs the build.
  • Test read access to the repository containing the Pod source as that same account.
  • Verify that the required credential is available to the job and scoped to the required read operation.
  • Inspect the error and access logs without printing the credential itself.

CocoaPods’ private Pods guide explains the repository configuration model. Follow the project’s chosen authentication method and the organization’s secret-handling policy. Do not place a token in a Podfile, a committed configuration file, or a shell command that CI may echo.

Use a controlled placeholder in internal runbooks and examples, such as $PRIVATE_REPO_TOKEN. The placeholder must not be replaced with a real token in captured logs. If the job cannot access a repository, first verify which credential the process received and which account the remote service evaluated; changing dependency versions will not repair a permission failure.

Credential boundary: A successful Specs lookup does not prove that the CI account can fetch the Pod’s source. Validate both repository access paths independently.

05

Podfile.lock: reproducible install vs. dependency update

The lockfile is part of the evidence. Confirm that Podfile.lock is committed, present in the runner checkout, and read from the intended project directory. A job launched from another directory can use a different Podfile or fail to find the expected lockfile.

The distinction between pod install and pod update is operationally important. CocoaPods explains that pod install uses the versions recorded in Podfile.lock where applicable, while pod update is used to update dependencies (command behavior guide). Therefore, an update command is not a neutral retry for a failed install.

Before editing dependencies, establish what the pipeline ran:

  • Check the exact command from the job log.
  • Verify the working directory and the paths to the Podfile and lockfile.
  • Check whether the lockfile is present in the checked-out revision.
  • Review whether a script or wrapper changes the command or its arguments.
  • Compare the lockfile before and after the run, if the job modifies it.
  • Record any source or authentication change separately from dependency changes.

If an update is genuinely needed, make it an intentional dependency change and review the resulting lockfile diff. Keep the install failure investigation separate. Updating dependencies can change the resolution result and remove the original condition that the team needed to diagnose.

06

Install completion: dependency setup vs. Xcode build

A successful pod install does not establish that the application builds. Conversely, a later Xcode error does not prove that CocoaPods installation failed. Treat dependency installation and Xcode integration as separate acceptance stages.

Check for evidence that CocoaPods completed its work: the command exit status, dependency fetch output, generated Pods project, and expected workspace integration. Then inspect the Xcode build log as a separate artifact. A compiler error, signing problem, or workspace-selection mistake may occur after dependency installation and needs its own diagnosis.

The CocoaPods command reference is useful when validating the command and its options. The CocoaPods project repository identifies the project as being in maintenance mode. That status is not evidence that a particular installation failure is unfixable, nor does it show that a remote runner caused the problem. Use the actual job output and current project documentation to decide what to change.

Use this checklist before closing the incident:

  • [ ] Reproduce the failure from the same commit and record the exact command.
  • [ ] Capture Ruby, RubyGems, Bundler, CocoaPods, PATH, account, and working-directory evidence from the CI process.
  • [ ] Confirm that the intended Gemfile, Gemfile.lock, Podfile, and Podfile.lock are present in the checkout.
  • [ ] Classify the earliest failure as command startup, resolution, HTTP/TLS/network access, private repository authorization, source download, or Xcode integration.
  • [ ] For private Pods, verify Specs access and source-repository access separately, without exposing secrets.
  • [ ] Change one identified cause at a time and keep the original lockfile unless a dependency update is an explicit change.
  • [ ] Rerun the same commit and confirm that installation completes before evaluating the Xcode build.
  • [ ] Save the passing command output and build result as evidence for the fix.

If the same commit still fails, return to the first failing layer in the logs. Recreating the runner or clearing every cache may discard useful evidence without addressing the underlying mismatch.

07

FAQ

Why does pod install work locally but fail on a remote Mac runner?

The environments may differ in Ruby, gem paths, shell initialization, working directory, or credentials. Compare the same commit and command, then collect the tool paths from inside the CI process. If the first error concerns a Specs request or source checkout, investigate network access and repository authorization separately instead of changing the Podfile immediately.

How can CI find the intended Ruby and pod command?

Run which ruby, which bundle, and which pod inside the job, and compare their output with the project’s intended toolchain. Check the CI working directory and whether it contains the expected Gemfile and Gemfile.lock. When the project uses Bundler, install that bundle and invoke CocoaPods through bundle exec so the job uses the project’s declared gems.

How can we distinguish a Specs CDN problem from a source or network configuration issue?

Use the first failing request as evidence. Check whether the error is a source-selection or resolution message, an HTTP response, or a TLS or connection failure. Confirm the Podfile’s sources and test access from the runner account. A single failed request does not prove a general CDN outage, so retain the response details and reproduce the issue before changing sources.

What should we check when CI cannot access a private Pod repository?

Verify the private Specs repository configuration and the Podfile source selection first. Then check read access to both the Specs repository and the Pod source repository under the CI execution account. Confirm that the expected credential is injected securely and available to that process. Keep token values out of commands, examples, and logs; an authorization failure is not a reason to update dependencies.

When the project and its credentials are correct but the team lacks a stable macOS execution environment, the next decision is operational: keep maintaining local or shared hardware, or use a remote Mac for the CI workload. Local hardware avoids remote access dependencies but must be purchased, maintained, and kept available. A shared runner can add queueing and account-boundary concerns. For temporary capacity or a controlled CI trial, review MESHLAUNCH remote Mac options and assess whether the access model fits the pipeline; teams comparing a hosted Mac mini workflow can also review MESHLAUNCH Mac mini options. Keep a local machine when the workload needs physical peripherals or sustained use that makes ownership the better fit.