Skip to content

feat: add copilot-persistence feature#10

Merged
rosstaco merged 5 commits intomainfrom
feat/copilot-persistence
Feb 12, 2026
Merged

feat: add copilot-persistence feature#10
rosstaco merged 5 commits intomainfrom
feat/copilot-persistence

Conversation

@rosstaco
Copy link
Copy Markdown
Owner

Persists GitHub Copilot CLI settings and chat history across container rebuilds using a named Docker volume mounted at /copilot-data with a symlink from ~/.copilot.

  • Add install script with root/non-root user handling
  • Add devcontainer-feature.json with volume mount and env var
  • Add tests (default, debian, ubuntu scenarios)
  • Update root README with feature documentation
  • Disable generate-docs to preserve custom feature READMEs

Persists GitHub Copilot CLI settings and chat history across container
rebuilds using a named Docker volume mounted at /copilot-data with a
symlink from ~/.copilot.

- Add install script with root/non-root user handling
- Add devcontainer-feature.json with volume mount and env var
- Add tests (default, debian, ubuntu scenarios)
- Update root README with feature documentation
- Disable generate-docs to preserve custom feature READMEs
Copy link
Copy Markdown

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 a new copilot-persistence devcontainer Feature intended to persist GitHub Copilot CLI state across container rebuilds by mounting a named Docker volume at /copilot-data and linking ~/.copilot to that volume.

Changes:

  • Introduces the copilot-persistence Feature (feature metadata, install script, and README).
  • Adds automated feature tests (default + Debian/Ubuntu scenarios).
  • Updates repository documentation and disables auto doc generation in the release workflow.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/copilot-persistence/devcontainer-feature.json Defines the feature metadata, volume mount to /copilot-data, and COPILOT_DATA_DIR env var.
src/copilot-persistence/install.sh Creates /copilot-data, sets ownership/permissions, and creates the ~/.copilot symlink.
src/copilot-persistence/README.md Documents how the feature works, what persists, and how to enable it.
test/copilot-persistence/test.sh Default scenario verification for mount/env var/symlink behavior.
test/copilot-persistence/debian.sh Debian scenario verification for mount/env var/symlink behavior.
test/copilot-persistence/ubuntu.sh Ubuntu scenario verification for mount/env var/symlink behavior.
test/copilot-persistence/scenarios.json Defines Debian/Ubuntu scenario images for feature testing.
README.md Adds top-level documentation for the new feature.
.github/workflows/release.yaml Disables generate-docs during publishing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/copilot-persistence/install.sh Outdated
Comment on lines +9 to +14
# Resolve home directory properly (root uses /root, others use /home/<user>)
if [ "$USERNAME" = "root" ]; then
USER_HOME="/root"
else
USER_HOME="/home/${USERNAME}"
fi
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

Home directory resolution assumes non-root users always live in /home/. This breaks for images/users with non-standard home paths (or when USERNAME doesn't exist). Resolve the home directory via the passwd database (e.g., getent/eval echo "~user") and fail fast if the user can't be resolved, rather than hardcoding /home.

Suggested change
# Resolve home directory properly (root uses /root, others use /home/<user>)
if [ "$USERNAME" = "root" ]; then
USER_HOME="/root"
else
USER_HOME="/home/${USERNAME}"
fi
# Resolve home directory via the passwd database or shell expansion
USER_HOME=""
if command -v getent >/dev/null 2>&1; then
USER_HOME="$(getent passwd "${USERNAME}" | cut -d: -f6)"
fi
if [ -z "${USER_HOME}" ]; then
USER_HOME="$(eval echo "~${USERNAME}" 2>/dev/null || true)"
fi
if [ -z "${USER_HOME}" ]; then
echo "Error: Unable to determine home directory for user '${USERNAME}'" >&2
exit 1
fi

Copilot uses AI. Check for mistakes.
Comment thread src/copilot-persistence/install.sh Outdated

# Fix ownership for the user
chown -R "${USER_UID}:${USER_GID}" /copilot-data
chmod 755 /copilot-data
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

/copilot-data is intended to store Copilot configuration and chat history, which can be sensitive. Setting permissions to 755 allows other users in the container to read it. Prefer 700 (or at least 750 with an appropriate group) after chowning to the target user.

Suggested change
chmod 755 /copilot-data
chmod 700 /copilot-data

Copilot uses AI. Check for mistakes.
Comment thread src/copilot-persistence/install.sh Outdated
Comment on lines +30 to +34
if [ ! -L "${USER_HOME}/.copilot" ]; then
rm -rf "${USER_HOME}/.copilot" 2>/dev/null || true
ln -sf /copilot-data "${USER_HOME}/.copilot"
chown -h "${USER_UID}:${USER_GID}" "${USER_HOME}/.copilot"
fi
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

The symlink creation guard only checks whether ~/.copilot is a symlink, not whether it points to /copilot-data. If a symlink already exists but targets a different path, the feature will silently leave it in place. Update the condition to also validate the symlink target and replace it when it’s not /copilot-data.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +6
{
"id": "copilot-persistence",
"version": "1.0.0",
"name": "Copilot CLI Persistence",
"description": "Persists GitHub Copilot CLI settings and chat history across rebuilds",
"mounts": [
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

All existing features in this repo include a documentationURL in devcontainer-feature.json, but this new feature does not. Add documentationURL pointing at src/copilot-persistence to match the repository convention and to improve discoverability in registries/tools.

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +12
"mounts": [
{
"source": "copilot-cli-data",
"target": "/copilot-data",
"type": "volume"
}
],
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

The volume name is hard-coded to copilot-cli-data, which means containers from unrelated projects on the same Docker host will share the same Copilot data by default. Consider adding a feature option to override the volume name (defaulting to the current value) so users can avoid cross-project data mixing when desired.

Copilot uses AI. Check for mistakes.
Comment thread README.md

```json
{
"features": {
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

The README usage example enables only copilot-persistence, but the text describes persisting GitHub Copilot CLI state. Since this feature is intended to be used alongside the Copilot CLI feature, update the example to include ghcr.io/devcontainers/features/copilot-cli:1 (or explicitly document that Copilot CLI must be installed separately).

Suggested change
"features": {
"features": {
"ghcr.io/devcontainers/features/copilot-cli:1": {},

Copilot uses AI. Check for mistakes.
Instead of chmod 777, add a /etc/profile.d script that fixes ownership
of /copilot-data on login when the volume mount overrides build-time
permissions.
- Resolve home directory via getent/passwd instead of hardcoding /home/<user>
- Use chmod 700 instead of 755 for sensitive copilot data
- Fix symlink guard to also verify target path points to /copilot-data
- Add documentationURL to devcontainer-feature.json
- Include copilot-cli feature in README usage example

Skipped configurable volume name - cross-project sharing is the intended
behavior for copilot data persistence.
Matches the shell-history pattern from stuartleeks/dev-container-features.
Each dev container gets its own isolated copilot data volume.
Copy link
Copy Markdown

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

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


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

echo "Error: Unable to determine home directory for user '${USERNAME}'" >&2
exit 1
fi

Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

USER_HOME fallback via eval echo "~${USERNAME}" can succeed even when the user doesn’t exist (it returns the literal string like ~someuser), so USER_HOME becomes a non-absolute path and the script may create /~someuser or similar and link config into the wrong place. Consider validating that the resolved home is an absolute existing directory (or that getent passwd/id succeeds for the user) and failing fast if the user cannot be resolved.

Suggested change
# Validate that USER_HOME is an absolute path to avoid creating paths like /~someuser
case "${USER_HOME}" in
/*)
# OK: absolute path
;;
*)
echo "Error: Resolved home directory '${USER_HOME}' for user '${USERNAME}' is not an absolute path" >&2
exit 1
;;
esac

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +42
#!/bin/bash
# Fix /copilot-data ownership if it exists and is not writable by current user
if [ -d /copilot-data ] && [ ! -w /copilot-data ]; then
sudo chown -R "$(id -u):$(id -g)" /copilot-data 2>/dev/null || true
fi
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

The generated init script runs sudo chown -R ... unconditionally and suppresses all errors. On images without sudo or where the user doesn’t have passwordless sudo, this will silently fail (or can block if sudo prompts), leaving /copilot-data non-writable and breaking the feature. Consider handling the cases explicitly: if running as root use chown directly; otherwise only run sudo if it’s present and non-interactive (e.g., sudo -n), and emit a clear warning/error when ownership cannot be fixed.

Copilot uses AI. Check for mistakes.
# Create a symlink from the default location to our persistent volume
# This handles the case where Copilot doesn't use XDG_CONFIG_HOME correctly
mkdir -p "${USER_HOME}"
if [ ! -L "${USER_HOME}/.copilot" ] || [ "$(readlink "${USER_HOME}/.copilot")" != "/copilot-data" ]; then
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

Replacing ~/.copilot via rm -rf can delete any pre-existing Copilot configuration/history that might already be present in the image or created earlier in the build. Consider migrating existing contents into /copilot-data (or at minimum backing them up and/or gating the removal behind an explicit check) before swapping to the symlink.

Suggested change
if [ ! -L "${USER_HOME}/.copilot" ] || [ "$(readlink "${USER_HOME}/.copilot")" != "/copilot-data" ]; then
if [ ! -L "${USER_HOME}/.copilot" ] || [ "$(readlink "${USER_HOME}/.copilot")" != "/copilot-data" ]; then
# If there is pre-existing Copilot data, back it up into /copilot-data before replacing it
if [ -e "${USER_HOME}/.copilot" ] && [ ! -L "${USER_HOME}/.copilot" ]; then
BACKUP_DIR="/copilot-data/preexisting-copilot-$(date +%s)"
mkdir -p "${BACKUP_DIR}" 2>/dev/null || true
# Copy contents rather than move to avoid failures across filesystems
cp -a "${USER_HOME}/.copilot/." "${BACKUP_DIR}/" 2>/dev/null || true
fi

Copilot uses AI. Check for mistakes.
publish-features: "true"
base-path-to-features: "./src"
generate-docs: "true"
generate-docs: "false"
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

Disabling generate-docs changes the release workflow behavior for all features and will stop updating the existing auto-generated feature READMEs (which currently indicate they’re generated from devcontainer-feature.json). If the goal is to keep a custom README for this feature, consider keeping generate-docs: "true" and using the devcontainers-supported NOTES.md mechanism for custom content, or otherwise updating the repo’s documentation approach consistently across features.

Suggested change
generate-docs: "false"
generate-docs: "true"

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +12
1. **Mounts a named volume** (scoped per dev container via `${devcontainerId}`) to `/copilot-data`
2. **Creates a symlink** from `~/.copilot` → `/copilot-data`
3. **Sets ownership** to the container user during installation (auto-detects from `$_REMOTE_USER`)

Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

The README doesn’t mention that installation replaces ~/.copilot with a symlink (and may remove an existing directory). It would be helpful to document this explicitly (including any migration/backup behavior) so users aren’t surprised by potential data loss when enabling the feature.

Copilot uses AI. Check for mistakes.
@rosstaco rosstaco merged commit b1f0d8c into main Feb 12, 2026
24 checks passed
@rosstaco rosstaco deleted the feat/copilot-persistence branch February 12, 2026 08:53
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