A Swift project can build successfully on your machine and still fail in a Codex cloud environment when Swift Package Manager tries to fetch a private dependency.
For example, a project might include this dependency:
.package(
url: "[email protected]:atacan/Deepgram.git",
branch: "main"
)Access to the task repository does not automatically authenticate Git and Swift Package Manager when they clone additional private repositories from inside the environment.
This post explains how to give Swift Package Manager access to private GitHub dependencies without changing the URLs in Package.swift.
The problem
Swift Package Manager uses Git to download source-control dependencies.
When it encounters this URL:
[email protected]:atacan/Deepgram.gitGit interprets it as an SSH connection. It expects an SSH private key associated with a GitHub account that can access the repository.
The Codex environment does not automatically expose your GitHub OAuth credentials as an SSH key or reusable Git credential. As a result, the main repository may be available while private package dependencies still fail to clone.
A typical failure looks like this:
[email protected]: Permission denied (publickey).
fatal: Could not read from remote repository.Instead of configuring SSH keys, we can use a fine-grained GitHub personal access token and transparently rewrite the SSH URL to HTTPS.
Create a fine-grained GitHub token
Open GitHub’s fine-grained personal access token creation page.
Choose the resource owner that owns the private package repository. Then configure the token as follows:
Repository access
Prefer:
Only select repositoriesSelect the private repositories that Swift Package Manager must download.
Selecting All repositories also works, but gives the token more access than is normally necessary.
Repository permissions
Click Add permissions and select:
Repository permissions
→ Contents
→ Read-onlyRead-only contents access is sufficient for cloning and resolving Swift packages.
You do not need account permissions. Only use read-and-write access when the environment must push commits or tags using this token.
Choose a reasonable expiration date and generate the token.
Add the token as a Codex secret
In the Codex environment settings, add the generated token under Secrets:
GITHUB_TOKENDo not add it as a normal environment variable. Tokens should be handled as secrets so they are not unnecessarily exposed in the environment or logs.
Never print the token during setup:
echo "$GITHUB_TOKEN"Also avoid enabling shell tracing with set -x, because commands containing credentials may be written to the build log.
Rewrite SSH URLs to HTTPS
We can tell Git to replace GitHub SSH URLs with equivalent HTTPS URLs.
These rules allow Package.swift to remain unchanged:
git config --global \
url."https://github.com/".insteadOf \
"[email protected]:"
git config --global --add \
url."https://github.com/".insteadOf \
"ssh://[email protected]/"After this configuration, Git internally converts:
[email protected]:atacan/Deepgram.gitto:
https://github.com/atacan/Deepgram.gitSwift Package Manager does not need to know that the rewrite happened.
Supply the token without storing it in the URL
It is possible to place a token directly in a Git URL, but doing so may leave the token in Git configuration, logs, process arguments, or diagnostic output.
A safer approach is to use a temporary GIT_ASKPASS script. Git calls this script when it needs a username or password.
Add this script to the environment setup step:
#!/usr/bin/env bash
set -euo pipefail
: "${GITHUB_TOKEN:?GITHUB_TOKEN is not available}"
# Rewrite GitHub SSH URLs used by Package.swift to HTTPS.
git config --global \
url."https://github.com/".insteadOf \
"[email protected]:"
git config --global --add \
url."https://github.com/".insteadOf \
"ssh://[email protected]/"
# Create a temporary credential provider.
ASKPASS_SCRIPT="$(mktemp)"
cleanup() {
rm -f "$ASKPASS_SCRIPT"
}
trap cleanup EXIT
cat > "$ASKPASS_SCRIPT" <<'EOF'
#!/usr/bin/env sh
case "$1" in
*Username*)
printf '%s\n' "x-access-token"
;;
*Password*)
printf '%s\n' "$GITHUB_TOKEN"
;;
esac
EOF
chmod 700 "$ASKPASS_SCRIPT"
export GIT_ASKPASS="$ASKPASS_SCRIPT"
export GIT_TERMINAL_PROMPT=0
# Optional authentication test.
git ls-remote \
[email protected]:atacan/Deepgram.git \
HEAD
# Resolve the project's Swift dependencies.
swift package resolveThe token is read from the secret environment variable while Git is running. It is not written into Package.swift, the repository URL, or the global Git configuration.
GIT_ASKPASS and the temporary script only exist for this setup run. Keep both git ls-remote and package resolution in the script so Git has access to the token whenever it needs to clone a dependency. Re-run setup after changing the token or adding another private package.
Verify the setup
The first line of the script requires GITHUB_TOKEN to be present:
: "${GITHUB_TOKEN:?GITHUB_TOKEN is not available}"If the secret is missing, setup stops with GITHUB_TOKEN is not available. It never prints the token value.
A successful response contains a commit hash followed by HEAD:
8a41f73e... HEADThat git ls-remote result verifies all of the important pieces:
- The SSH URL was rewritten to HTTPS.
- Git received the token.
- The token has access to the repository.
- GitHub accepted the token’s permissions.
The subsequent swift package resolve downloads the dependencies for a standalone Swift package. Add -v while diagnosing a failure to see which repository or revision caused it.
Resolving packages for Xcode projects
For a standalone Swift package, use:
swift package resolveFor an Xcode project:
xcodebuild \
-resolvePackageDependencies \
-project YourProject.xcodeprojFor a workspace:
xcodebuild \
-resolvePackageDependencies \
-workspace YourProject.xcworkspace \
-scheme YourSchemeRun package resolution during environment setup so the private dependencies are downloaded before Codex begins work or runs builds. Use the command that matches the project type in the setup script, while GIT_ASKPASS is available.
Common errors
Permission denied (publickey)
Git is still attempting to use SSH.
Check the rewrite rules:
git config --global --get-regexp '^url\..*\.insteadOf$'You should see entries for both:
[email protected]:
ssh://[email protected]/Repository not found
GitHub often returns this message when authentication fails, even when the repository exists.
Check that:
- The token’s resource owner is correct.
- The private repository was selected.
Contents: Read-onlywas added.- The token has not expired.
- The repository URL and capitalization are correct.
- The organization has approved the token, where approval is required.
The setup script says GITHUB_TOKEN is missing
Confirm that the value was added under Secrets, that the variable name is exactly GITHUB_TOKEN, and that the setup script can access it.
One private package works, but another fails
A fine-grained token only has access to the repositories selected when it was created. Add the missing repository to the token’s repository access or create a new token with the correct scope.
Final Package.swift
No changes are required to the dependency declaration:
dependencies: [
.package(
url: "[email protected]:atacan/Deepgram.git",
branch: "main"
)
]The Git rewrite happens below Swift Package Manager, so the same SSH URL can continue to work locally while the Codex environment uses HTTPS and a token.
Conclusion
For private Swift package dependencies, the setup is:
- Create a fine-grained GitHub token with read-only contents access.
- Store it as a Codex secret named
GITHUB_TOKEN. - Rewrite GitHub SSH URLs to HTTPS.
- Provide the token through a temporary credential helper.
- Resolve Swift packages during environment setup.
This keeps Package.swift unchanged, limits the token’s permissions, and avoids permanently storing credentials in the repository or Git configuration.