Skip to content

feat(agentapi): add agentapi_cache_dir for persistent binary caching#769

Open
iamriajul wants to merge 2 commits intocoder:mainfrom
iamriajul:feat/agentapi-cache-dir
Open

feat(agentapi): add agentapi_cache_dir for persistent binary caching#769
iamriajul wants to merge 2 commits intocoder:mainfrom
iamriajul:feat/agentapi-cache-dir

Conversation

@iamriajul
Copy link
Contributor

Description

Add an opt-in agentapi_cache_dir variable to avoid re-downloading the AgentAPI binary on every workspace start. When set, the binary is stored in that directory after the first download and reused on subsequent starts — useful with a shared persistent volume.

The binary is cached with a versioned filename (e.g. agentapi-linux-amd64-v0.10.0) so multiple versions coexist safely. When agentapi_version = "latest", the actual release tag is resolved via GitHub's redirect before computing the cache key, so the cache never gets stuck on a stale binary. The resolution request is skipped entirely when agentapi_cache_dir is not set.

Type of Change

  • New module
  • New template
  • Bug fix
  • Feature/enhancement
  • Documentation
  • Other

Module Information

Path: registry/coder/modules/agentapi
New version: v2.2.0
Breaking change: [ ] Yes [x] No

Testing & Validation

  • Tests pass (bun test — all agentapi-specific tests pass)
  • Code formatted (bun fmt)
  • Changes tested locally

Related Issues

None

Add an opt-in agentapi_cache_dir variable. When set, the AgentAPI binary
is stored in that directory after the first download and reused on
subsequent workspace starts — useful with a shared persistent volume to
avoid repeated GitHub downloads.

The binary is cached under a versioned filename (e.g.
agentapi-linux-amd64-v0.10.0) so multiple versions can coexist. When
agentapi_version is "latest", the actual release tag is resolved via
GitHub's redirect before computing the cache key, preventing the cache
from getting stuck on a stale binary. The resolution request is skipped
entirely when agentapi_cache_dir is not set.

Bumps version 2.1.1 → 2.2.0.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in persistent caching mechanism for the AgentAPI binary in the agentapi Terraform module to avoid repeated downloads on workspace start, with tests and documentation updates.

Changes:

  • Introduces agentapi_cache_dir Terraform variable and wires it into the install/start script.
  • Updates main.sh to reuse a cached versioned AgentAPI binary (and resolve latest to a concrete tag for cache keying).
  • Adds module tests and README documentation for the new caching behavior.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
registry/coder/modules/agentapi/scripts/main.sh Adds cache lookup/save logic and latest version resolution for cache key stability.
registry/coder/modules/agentapi/main.tf Adds agentapi_cache_dir input and passes it to the startup script.
registry/coder/modules/agentapi/main.test.ts Adds tests validating cache-hit and cache-save behavior.
registry/coder/modules/agentapi/README.md Bumps module version and documents binary caching usage.
.gitignore Ignores .serena directory.

Comment on lines 89 to 92
"https://github.com/coder/agentapi/releases/latest" | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' || echo "latest")
echo "Resolved AgentAPI latest version to: ${resolved_version}"
fi
cached_binary="${CACHE_DIR}/${binary_name}-${resolved_version}"
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When resolving latest, the fallback || echo "latest" means a transient network/redirect failure will cache under the literal key "latest" and can pin the cache to a stale binary (contradicting the intended "never stale" behavior). Consider disabling caching for that run when the tag cannot be resolved (e.g., leave cached_binary empty if resolved_version is still "latest"), or treat resolution failure as a warning and proceed without writing/using a cache entry.

Suggested change
"https://github.com/coder/agentapi/releases/latest" | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' || echo "latest")
echo "Resolved AgentAPI latest version to: ${resolved_version}"
fi
cached_binary="${CACHE_DIR}/${binary_name}-${resolved_version}"
"https://github.com/coder/agentapi/releases/latest" | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' || true)
if [ -z "${resolved_version}" ]; then
echo "Warning: Failed to resolve latest AgentAPI version tag; proceeding without cache for this run."
else
echo "Resolved AgentAPI latest version to: ${resolved_version}"
fi
fi
if [ -n "${resolved_version}" ] && [ "${resolved_version}" != "latest" ]; then
cached_binary="${CACHE_DIR}/${binary_name}-${resolved_version}"
else
cached_binary=""
fi

Copilot uses AI. Check for mistakes.
"https://github.com/coder/agentapi/releases/latest" | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' || echo "latest")
echo "Resolved AgentAPI latest version to: ${resolved_version}"
fi
cached_binary="${CACHE_DIR}/${binary_name}-${resolved_version}"
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved_version is derived from AGENTAPI_VERSION and then interpolated into a filesystem path. Because agentapi_version can be an arbitrary string, this can introduce path traversal or invalid filenames (e.g., containing /, .., newlines) and cause writes outside CACHE_DIR when caching. Please sanitize the version component before building cached_binary (or restrict agentapi_version to a safe pattern when agentapi_cache_dir is set).

Suggested change
cached_binary="${CACHE_DIR}/${binary_name}-${resolved_version}"
# Sanitize the resolved version so it is safe to use as a filename component.
# Allow only alphanumerics, dots, underscores, and hyphens; replace others with '_'.
safe_resolved_version=$(printf '%s' "${resolved_version}" | tr -c 'A-Za-z0-9._-' '_')
if [ -z "${safe_resolved_version}" ]; then
echo "Warning: Resolved AgentAPI version '${resolved_version}' is empty after sanitization; skipping cache."
cached_binary=""
else
cached_binary="${CACHE_DIR}/${binary_name}-${safe_resolved_version}"
fi

Copilot uses AI. Check for mistakes.
Comment on lines 114 to 115
mkdir -p "${CACHE_DIR}"
cp agentapi "${cached_binary}"
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caching is an optimization, but with set -e a failure in mkdir -p "${CACHE_DIR}" or cp agentapi "${cached_binary}" will abort the whole install/start even though the binary was successfully downloaded. Make cache writes best-effort (emit a warning and continue) so a misconfigured or read-only cache dir doesn’t break workspace startup.

Suggested change
mkdir -p "${CACHE_DIR}"
cp agentapi "${cached_binary}"
if ! mkdir -p "${CACHE_DIR}"; then
echo "Warning: Failed to create cache directory ${CACHE_DIR}. Continuing without caching."
elif ! cp agentapi "${cached_binary}"; then
echo "Warning: Failed to cache AgentAPI binary to ${cached_binary}. Continuing without caching."
fi

Copilot uses AI. Check for mistakes.
if [ -n "${cached_binary}" ]; then
echo "Caching AgentAPI binary to ${cached_binary}"
mkdir -p "${CACHE_DIR}"
cp agentapi "${cached_binary}"
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Writing the cache file via cp agentapi "${cached_binary}" is not atomic. If multiple workspaces share the same persistent cache volume, concurrent starts can race and one workspace may read a partially-written binary. Consider writing to a temp file in CACHE_DIR and then mv-ing into place (atomic rename), optionally with a lock to serialize writers.

Suggested change
cp agentapi "${cached_binary}"
tmp_cached_binary="${cached_binary}.$$"
cp agentapi "${tmp_cached_binary}"
mv -f "${tmp_cached_binary}" "${cached_binary}"

Copilot uses AI. Check for mistakes.
ARG_AGENTAPI_CHAT_BASE_PATH='${local.agentapi_chat_base_path}' \
ARG_TASK_ID='${try(data.coder_task.me.id, "")}' \
ARG_TASK_LOG_SNAPSHOT='${var.task_log_snapshot}' \
ARG_CACHE_DIR='${var.agentapi_cache_dir}' \
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agentapi_cache_dir is interpolated directly into a single-quoted shell assignment. If the path contains a single quote or newline, it can break the generated script. Consider passing this value the same way as ARG_WORKDIR (base64-encoded/decoded) or otherwise escaping it before embedding it into the heredoc.

Suggested change
ARG_CACHE_DIR='${var.agentapi_cache_dir}' \
ARG_CACHE_DIR="$(echo -n '${base64encode(var.agentapi_cache_dir)}' | base64 -d)" \

Copilot uses AI. Check for mistakes.
Comment on lines 168 to 173
const cachedBinary = `${cacheDir}/agentapi-linux-amd64-${pinnedVersion}`;
await execContainer(id, [
"bash",
"-c",
`mkdir -p ${cacheDir} && cp /usr/bin/agentapi ${cachedBinary}`,
]);
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test hardcodes the cached binary name as agentapi-linux-amd64-..., but the install script selects agentapi-linux-arm64 on aarch64. On ARM hosts (e.g., Apple Silicon / Colima), this will miss the cache and exercise the download path unexpectedly. Consider deriving binary_name by querying uname -m inside the container and building the expected cache filename accordingly.

Copilot uses AI. Check for mistakes.
},
});

const cachedBinary = `${cacheDir}/agentapi-linux-amd64-${pinnedVersion}`;
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test assumes the cached filename uses agentapi-linux-amd64-..., which will fail on ARM (aarch64) where the script caches agentapi-linux-arm64-.... Consider computing the expected filename from the container architecture so the test is portable across dev environments.

Suggested change
const cachedBinary = `${cacheDir}/agentapi-linux-amd64-${pinnedVersion}`;
// Determine the container architecture so the expected cached filename
// matches how the script names the AgentAPI binary.
const archResult = await execContainer(id, ["uname", "-m"]);
expect(archResult.exitCode).toBe(0);
const arch = archResult.stdout.trim();
const agentArch =
arch === "aarch64" || arch === "arm64" ? "linux-arm64" : "linux-amd64";
const cachedBinary = `${cacheDir}/agentapi-${agentArch}-${pinnedVersion}`;

Copilot uses AI. Check for mistakes.
- Pass agentapi_cache_dir base64-encoded to avoid single-quote injection
  in the heredoc (same pattern as ARG_WORKDIR)
- Skip cache entirely if latest version resolution fails instead of
  falling back to the literal string "latest" as a cache key
- Sanitize resolved_version before using it as a filename component to
  prevent path traversal (tr -c 'A-Za-z0-9._-' '_')
- Make cache writes best-effort: warn and continue if mkdir or cp fails
  so a misconfigured/read-only cache dir doesn't abort workspace startup
- Write cache atomically via cp + mv to avoid concurrent workspaces on a
  shared volume observing a partially-written binary
- Derive container architecture in tests via uname -m so cache filename
  is correct on both amd64 and arm64 hosts

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
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.

2 participants