diff --git a/modules/end-user-guide/nav.adoc b/modules/end-user-guide/nav.adoc index 67375c870a..ad1e63b669 100644 --- a/modules/end-user-guide/nav.adoc +++ b/modules/end-user-guide/nav.adoc @@ -40,6 +40,11 @@ * xref:using-ai-assistants-in-workspaces.adoc[] ** xref:configuring-an-ai-provider-api-key.adoc[] ** xref:changing-the-ai-tool-on-a-workspace.adoc[] +** xref:persist-claude-code-configuration.adoc[] +*** xref:share-claude-code-configuration-with-pvc-direct-mount.adoc[] +*** xref:share-claude-code-configuration-with-pvc-tmp-copy.adoc[] +*** xref:share-claude-code-configuration-with-gitops.adoc[] +*** xref:troubleshoot-claude-code-configuration-persistence.adoc[] * xref:using-credentials-and-configurations-in-workspaces.adoc[] ** xref:mounting-secrets.adoc[] *** xref:creating-image-pull-secrets.adoc[] diff --git a/modules/end-user-guide/pages/persist-claude-code-configuration.adoc b/modules/end-user-guide/pages/persist-claude-code-configuration.adoc new file mode 100644 index 0000000000..dd0be2788d --- /dev/null +++ b/modules/end-user-guide/pages/persist-claude-code-configuration.adoc @@ -0,0 +1,110 @@ +:_content-type: CONCEPT +:description: Persist and share Claude Code configuration across {prod-short} workspaces. +:keywords: user-guide, claude-code, ai, configuration, persistence, pvc +:navtitle: Persist Claude Code configuration +:page-aliases: persisting-claude-code-configuration.adoc + +[id="persist-claude-code-configuration"] += Persist Claude Code configuration in {prod-short} workspaces + +To reuse Claude Code skills, settings, MCP servers, and plugins across {prod-short} workspaces, persist the configuration in a shared volume or a Git repository. Without persistence, each new workspace requires a fresh setup. + +== Claude Code configuration files + +Claude Code stores configuration in two separate locations in the user's home directory: + +|=== +| Path | Contents + +| `{prod-home}/.claude/` +| Settings, plugins, custom agents, hooks, per-project memory. + +| `{prod-home}/.claude.json` +| Application settings and MCP server configurations. +|=== + +[IMPORTANT] +==== +MCP server configurations are stored in `{prod-home}/.claude.json`, a separate file at the home directory root, not inside `{prod-home}/.claude/`. Both locations must be persisted. + +Claude Code writes runtime state to both paths during every session, including session files, caches, and usage statistics. These paths must be writable. Mounting a read-only ConfigMap or Secret directly to these paths causes Claude Code to fail. +==== + +== Available approaches + +|=== +| Approach | Summary + +| xref:share-claude-code-configuration-with-pvc-direct-mount.adoc[] +| Both `{prod-home}/.claude/` and `{prod-home}/.claude.json` are mounted directly from a dedicated PVC. All changes persist automatically. Requires a one-time init pod to pre-create `.claude.json`. + +| xref:share-claude-code-configuration-with-pvc-tmp-copy.adoc[] +| A dedicated PVC is mounted at `/tmp/claude`. A `postStart` command copies everything into the home directory. No init pod is required, but changes must be synced back manually before stopping the workspace. + +| xref:share-claude-code-configuration-with-gitops.adoc[] +| A Git repository stores the baseline Claude Code configuration. The {devworkspace} Operator clones the repository at startup, and a `postStart` command copies the configuration into the home directory. +|=== + +== Claude Code file reference + +|=== +| Path | Purpose | Size | Regeneratable + +| `{prod-home}/.claude/settings.json` +| Permissions, hooks, enabled plugins, model preferences. +| Small +| No + +| `{prod-home}/.claude/settings.local.json` +| Local setting overrides. +| Small +| No + +| `{prod-home}/.claude/CLAUDE.md` +| User-level instructions for Claude. +| Small +| No + +| `{prod-home}/.claude/plugins/` +| Installed plugins and caches. +| Large (100{nbsp}MB+) +| Partially + +| `{prod-home}/.claude/agents/` +| Custom agent definitions. +| Small +| No + +| `{prod-home}/.claude/hooks/` +| Shell scripts for session lifecycle. +| Small +| No + +| `{prod-home}/.claude/projects/` +| Per-project memory and settings. +| Medium +| No + +| `{prod-home}/.claude.json` +| Application settings and MCP server configurations. +| Small +| No + +| `{prod-home}/.claude/history.jsonl` +| Conversation history. +| Large +| Optional + +| `{prod-home}/.claude/plugins/cache/` +| Plugin download cache. +| Large +| Yes +|=== + +.Additional resources + +* xref:share-claude-code-configuration-with-pvc-direct-mount.adoc[] +* xref:share-claude-code-configuration-with-pvc-tmp-copy.adoc[] +* xref:share-claude-code-configuration-with-gitops.adoc[] +* xref:troubleshoot-claude-code-configuration-persistence.adoc[] +* link:https://github.com/devfile/devworkspace-operator/blob/main/docs/additional-configuration.adoc#automatically-mounting-volumes-configmaps-and-secrets[Automatically mounting volumes, configmaps, and secrets] diff --git a/modules/end-user-guide/pages/share-claude-code-configuration-with-gitops.adoc b/modules/end-user-guide/pages/share-claude-code-configuration-with-gitops.adoc new file mode 100644 index 0000000000..d07801961a --- /dev/null +++ b/modules/end-user-guide/pages/share-claude-code-configuration-with-gitops.adoc @@ -0,0 +1,117 @@ +:_content-type: PROCEDURE +:description: Share Claude Code configuration across {prod-short} workspaces by storing it in a Git repository. +:keywords: user-guide, claude-code, ai, configuration, persistence, gitops, git +:navtitle: Share configuration with GitOps +:page-aliases: sharing-claude-code-configuration-with-gitops.adoc + +[id="share-claude-code-configuration-with-gitops"] += Share Claude Code configuration across workspaces with GitOps + +Instead of using a shared PVC, you can store Claude Code configuration in a Git repository and pull it into workspaces at startup. Each workspace gets its own isolated, writable copy of the configuration. + +This approach eliminates multi-attach errors and last-write-wins data corruption risks that can occur with shared PVCs on clusters without RWX storage. + +== How it works + +A central Git repository stores the baseline Claude Code configuration. The repository is listed in the `projects` section of the devfile, and the {devworkspace} Operator's project-clone init container clones it automatically at workspace startup. A `postStart` command then copies the configuration files from the cloned repository into the home directory. + +Two usage patterns are possible: + +* *Read-only baseline* — administrators maintain a shared repository with team-wide settings such as security hooks, API proxies, and approved plugins. The configuration is cloned at startup and copied into the home directory. Developers do not push changes back. This is suitable for enforcing organization-wide policies. + +* *User-specific persistence* — each developer maintains a private branch or repository with personal configuration. The repository is cloned at startup and copied into the home directory. Optionally, a `preStop` command or manual step commits and pushes changes back before the workspace stops. + +For a comparison with other approaches, see xref:persist-claude-code-configuration.adoc[]. + +.Prerequisites + +* An active `{orch-cli}` session with permissions to create resources in your {orch-namespace}. See {orch-cli-link}. + +* Claude Code installed in the workspace container image. + +* A Git repository containing Claude Code configuration files. The repository must be accessible from the workspace. At minimum, the repository should contain: ++ +---- +.claude/ + settings.json + CLAUDE.md +.claude.json +---- + +* Git credentials configured in the workspace if the repository is private. See xref:authenticating-to-a-git-server-from-a-workspace.adoc[]. + +.Procedure + +. Add the configuration repository to the `projects` section of your devfile: ++ +==== +[source,yaml,subs="+quotes,+attributes"] +---- +projects: + - name: claude-config + git: + remotes: + origin: '____' # <1> +---- +<1> Replace with the URL of your Claude Code configuration repository. +==== + +. Add a `postStart` command to copy the configuration from the cloned repository into the home directory: ++ +==== +[source,yaml,subs="+quotes,+attributes"] +---- +commands: + - id: init-claude-config + exec: + commandLine: | + cp -a /projects/claude-config/.claude/. {prod-home}/.claude/ 2>/dev/null || true + cp /projects/claude-config/.claude.json {prod-home}/.claude.json 2>/dev/null || true + component: ____ # <1> +events: + postStart: + - init-claude-config +---- +<1> Replace with the name of your editor container component in the devfile. +==== + +. Start the workspace. + +. Optional: To persist changes back to the Git repository before stopping, commit and push from within the workspace: ++ +[subs="+quotes,+attributes"] +---- +$ cp -a {prod-home}/.claude/. /projects/claude-config/.claude/ +$ cp {prod-home}/.claude.json /projects/claude-config/.claude.json +$ cd /projects/claude-config +$ git add -A && git commit -m "Update Claude Code configuration" && git push +---- + +.Verification + +. Start a workspace with the devfile containing the configuration repository. +. Verify Claude Code starts with the expected configuration (settings, MCP servers, plugins). +. Make a configuration change, sync back to the repository, and push. +. Start a new workspace with the same devfile. Verify the updated configuration is available. + +== Advantages + +* No RWX storage requirement. Each workspace uses its own local storage. +* No multi-attach errors. Concurrent workspaces are not affected. +* Git history provides an audit trail of configuration changes. +* Works across clusters and {prod-short} instances. + +== Limitations + +* Requires a Git repository accessible from the workspace. +* Changes must be committed and pushed to persist. Unsaved changes are lost when the workspace stops. +* Large files such as plugin caches (`{prod-home}/.claude/plugins/cache/`) are not suitable for Git storage. Consider adding them to `.gitignore`. +* Merge conflicts can occur when multiple workspaces push changes to the same branch concurrently. + +.Additional resources + +* xref:persist-claude-code-configuration.adoc[] +* xref:share-claude-code-configuration-with-pvc-direct-mount.adoc[] +* xref:share-claude-code-configuration-with-pvc-tmp-copy.adoc[] +* xref:troubleshoot-claude-code-configuration-persistence.adoc[] +* xref:authenticating-to-a-git-server-from-a-workspace.adoc[] diff --git a/modules/end-user-guide/pages/share-claude-code-configuration-with-pvc-direct-mount.adoc b/modules/end-user-guide/pages/share-claude-code-configuration-with-pvc-direct-mount.adoc new file mode 100644 index 0000000000..0f2c82a866 --- /dev/null +++ b/modules/end-user-guide/pages/share-claude-code-configuration-with-pvc-direct-mount.adoc @@ -0,0 +1,186 @@ +:_content-type: PROCEDURE +:description: Share Claude Code configuration across {prod-short} workspaces by mounting a dedicated PVC directly to the configuration paths. +:keywords: user-guide, claude-code, ai, configuration, persistence, pvc, direct-mount +:navtitle: Share configuration with PVC direct mount +:page-aliases: + +[id="share-claude-code-configuration-with-pvc-direct-mount"] += Share Claude Code configuration with PVC direct mount + +Both `{prod-home}/.claude/` and `{prod-home}/.claude.json` are mounted directly from a dedicated PVC. All changes persist automatically. + +The direct mount flow: + +. A one-time init pod creates an empty skeleton on the PVC: `{}` in `.claude.json` and an empty `.claude/` directory. +. The first workspace starts. The PVC mounts to `{prod-home}/.claude/` and `{prod-home}/.claude.json`. Claude Code sees valid, empty configuration. +. You configure Claude Code — install plugins, add MCP servers, change settings. All writes go directly to the PVC. +. The second workspace starts. The same PVC mounts, and all configuration from the previous workspace is available. + +For a comparison with other approaches, see xref:persist-claude-code-configuration.adoc[]. + +.Prerequisites + +* An active `{orch-cli}` session with permissions to create PVCs in your {orch-namespace}. See {orch-cli-link}. + +* Claude Code installed in the workspace container image. + +* For concurrent workspaces: a storage class that supports `ReadWriteMany` (RWX) access mode, such as AWS EFS or NFS. Standard block storage (gp2, gp3) supports only `ReadWriteOnce` (RWO). + +* All running workspaces must be stopped before creating the PVC. If a workspace is running when the PVC with the automount label is created, `{prod-home}/.claude.json` may be mounted as a directory instead of a file. + +.Procedure + +. Create a file `claude-config-pvc.yaml` with the following PVC definition: ++ +==== +[source,yaml,subs="+quotes,+attributes"] +---- +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: claude-config + annotations: + controller.devfile.io/mount-path: >- + [{"path":"{prod-home}/.claude","subPath":".claude"}, + {"path":"{prod-home}/.claude.json","subPath":".claude.json"}] + labels: + controller.devfile.io/mount-to-devworkspace: 'true' +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 1Gi +---- +==== + +. Apply the PVC to your {orch-namespace}: ++ +[subs="+quotes,+attributes"] +---- +$ {orch-cli} apply -f claude-config-pvc.yaml -n ____ +---- + +. Create the init pod to initialize the PVC. ++ +The `{prod-home}/.claude.json` subPath target must exist as a *file* on the PVC before any workspace starts. Without this step, {kubernetes} kubelet creates a directory instead of a file, and Claude Code fails to parse it. ++ +Create a file `claude-config-init-pod.yaml`: ++ +==== +[source,yaml,subs="+quotes,+attributes"] +---- +apiVersion: v1 +kind: Pod +metadata: + name: claude-config-init +spec: + restartPolicy: Never + containers: + - image: registry.access.redhat.com/ubi9/ubi-minimal + name: init + command: + - sh + - -c + - | + mkdir -p /mnt/.claude + echo '{}' > /mnt/.claude.json + echo "=== Initialization complete ===" + ls -la /mnt/ + volumeMounts: + - mountPath: /mnt + name: vol + volumes: + - name: vol + persistentVolumeClaim: + claimName: claude-config +---- +==== + +. Apply the init pod: ++ +[subs="+quotes,+attributes"] +---- +$ {orch-cli} apply -f claude-config-init-pod.yaml -n ____ +---- + +. Wait for the pod to complete: ++ +[subs="+quotes,+attributes"] +---- +$ {orch-cli} wait pod/claude-config-init --for=condition=Ready --timeout=120s +---- + +. Verify the initialization: ++ +[subs="+quotes,+attributes"] +---- +$ {orch-cli} logs claude-config-init +---- + +. Delete the init pod: ++ +[subs="+quotes,+attributes"] +---- +$ {orch-cli} delete pod claude-config-init +---- + +. Start any workspace. The PVC auto-mounts into every workspace pod. + +[WARNING] +==== +On multi-AZ clusters using `WaitForFirstConsumer` storage classes (gp2, gp3), the init pod may bind the PV to a different availability zone than workspace pods. See xref:troubleshoot-claude-code-configuration-persistence.adoc#pvc-scheduling-failure-after-init-pod-setup[PVC scheduling failure after init pod setup]. +==== + +.Verification + +. Start a workspace and configure Claude Code (add an MCP server, install a plugin, or modify settings). +. Stop and restart the workspace. Verify the configuration is preserved. +. Start a different workspace. Verify the same configuration is available. + +== Filtering by workspace name + +Control which workspaces mount the PVC by adding annotations: + +[source,yaml,subs="+quotes,+attributes"] +---- +annotations: + controller.devfile.io/mount-to-devworkspace-include: '____' # <1> + controller.devfile.io/mount-to-devworkspace-exclude: '____' # <2> +---- +<1> Mount the PVC only to workspaces whose names match the pattern. +<2> Mount the PVC to all workspaces except those whose names match the pattern. + +Supported patterns: exact match (`name`), prefix (`name\*`), suffix (`\*name`), contains (`\*name\*`). Matching is on the {devworkspace} resource name. + +== Concurrent workspaces + +|=== +| Access mode | Behavior + +| ReadWriteOnce (RWO) +| One workspace at a time. Two workspaces on different nodes cause a multi-attach error. + +| ReadWriteMany (RWX) +| Concurrent workspaces work. Requires a storage class that supports RWX, such as AWS EFS or NFS. Standard block storage (gp2, gp3) does not support RWX. +|=== + +== Compatibility with `persistUserHome` + +A dedicated PVC mounted at `{prod-home}/.claude` and the persistent home PVC mounted at `{prod-home}/` coexist correctly as nested mounts. Writes to `{prod-home}/.claude/` go to the dedicated PVC. Writes to other paths under `{prod-home}/` go to the persistent home PVC. + +If you only need persistence within a single workspace and not cross-workspace sharing, `persistUserHome` alone is sufficient. + +== Limitations + +* Requires manual PVC setup per user {orch-namespace}. +* RWX storage may not be available on all clusters. +* On clusters with only block storage and multi-AZ topology, the init pod may cause scheduling failures. Use the xref:share-claude-code-configuration-with-pvc-tmp-copy.adoc[tmp copy] approach instead. + +.Additional resources + +* xref:persist-claude-code-configuration.adoc[] +* xref:share-claude-code-configuration-with-pvc-tmp-copy.adoc[] +* xref:share-claude-code-configuration-with-gitops.adoc[] +* xref:troubleshoot-claude-code-configuration-persistence.adoc[] +* xref:requesting-persistent-storage-for-workspaces.adoc[] diff --git a/modules/end-user-guide/pages/share-claude-code-configuration-with-pvc-tmp-copy.adoc b/modules/end-user-guide/pages/share-claude-code-configuration-with-pvc-tmp-copy.adoc new file mode 100644 index 0000000000..338e039a1b --- /dev/null +++ b/modules/end-user-guide/pages/share-claude-code-configuration-with-pvc-tmp-copy.adoc @@ -0,0 +1,142 @@ +:_content-type: PROCEDURE +:description: Share Claude Code configuration across {prod-short} workspaces by copying files from a dedicated PVC mounted at /tmp. +:keywords: user-guide, claude-code, ai, configuration, persistence, pvc, tmp-copy +:navtitle: Share configuration with PVC tmp copy +:page-aliases: + +[id="share-claude-code-configuration-with-pvc-tmp-copy"] += Share Claude Code configuration with PVC tmp copy + +The entire PVC is mounted at `/tmp/claude`. A `postStart` command copies everything into the home directory. No init pod is required and there are no subPath limitations. Changes must be synced back manually before stopping the workspace. + +The tmp copy flow: + +. The first workspace starts. The PVC mounts at `/tmp/claude` (empty on first run). The `postStart` command copies configuration into `{prod-home}/.claude/` and `{prod-home}/.claude.json`. On first run, nothing is copied. +. You configure Claude Code — install plugins, add MCP servers, change settings. All writes go to the home directory, not the PVC. +. Before stopping the workspace, you sync changes back to the PVC at `/tmp/claude`. +. The second workspace starts. The `postStart` command copies the saved configuration from the PVC into the home directory. + +For a comparison with other approaches, see xref:persist-claude-code-configuration.adoc[]. + +.Prerequisites + +* An active `{orch-cli}` session with permissions to create PVCs in your {orch-namespace}. See {orch-cli-link}. + +* Claude Code installed in the workspace container image. + +* For concurrent workspaces: a storage class that supports `ReadWriteMany` (RWX) access mode, such as AWS EFS or NFS. Standard block storage (gp2, gp3) supports only `ReadWriteOnce` (RWO). + +.Procedure + +. Create a file `claude-config-pvc.yaml` with the following PVC definition: ++ +==== +[source,yaml,subs="+quotes,+attributes"] +---- +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: claude-config + annotations: + controller.devfile.io/mount-path: '[{"path":"/tmp/claude","subPath":"claude"}]' + labels: + controller.devfile.io/mount-to-devworkspace: 'true' +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 1Gi +---- +==== + +. Apply the PVC to your {orch-namespace}: ++ +[subs="+quotes,+attributes"] +---- +$ {orch-cli} apply -f claude-config-pvc.yaml -n ____ +---- + +. Add a `postStart` command to your devfile to copy all configuration from the PVC into the home directory: ++ +==== +[source,yaml,subs="+quotes,+attributes"] +---- +commands: + - id: init-claude-config + exec: + commandLine: | + mkdir -p {prod-home}/.claude + cp -a /tmp/claude/.claude/. {prod-home}/.claude/ 2>/dev/null || true + cp /tmp/claude/.claude.json {prod-home}/.claude.json 2>/dev/null || true + component: ____ # <1> +events: + postStart: + - init-claude-config +---- +<1> Replace with the name of your editor container component in the devfile. +==== + +. Start the workspace. + +. Before stopping the workspace, sync changes back to the PVC: ++ +[subs="+quotes,+attributes"] +---- +$ cp -a {prod-home}/.claude/. /tmp/claude/.claude/ && \ + cp {prod-home}/.claude.json /tmp/claude/.claude.json +---- + +.Verification + +. Start a workspace and configure Claude Code. +. Sync changes back to the PVC. +. Start a different workspace. +. Verify that the Claude Code configuration from the first workspace is available. + +== Filtering by workspace name + +Control which workspaces mount the PVC by adding annotations: + +[source,yaml,subs="+quotes,+attributes"] +---- +annotations: + controller.devfile.io/mount-to-devworkspace-include: '____' # <1> + controller.devfile.io/mount-to-devworkspace-exclude: '____' # <2> +---- +<1> Mount the PVC only to workspaces whose names match the pattern. +<2> Mount the PVC to all workspaces except those whose names match the pattern. + +Supported patterns: exact match (`name`), prefix (`name\*`), suffix (`\*name`), contains (`\*name\*`). Matching is on the {devworkspace} resource name. + +== Concurrent workspaces + +|=== +| Access mode | Behavior + +| ReadWriteOnce (RWO) +| One workspace at a time. Two workspaces on different nodes cause a multi-attach error. + +| ReadWriteMany (RWX) +| Concurrent workspaces work. Requires a storage class that supports RWX, such as AWS EFS or NFS. Standard block storage (gp2, gp3) does not support RWX. +|=== + +== Compatibility with `persistUserHome` + +A dedicated PVC mounted at `{prod-home}/.claude` and the persistent home PVC mounted at `{prod-home}/` coexist correctly as nested mounts. Writes to `{prod-home}/.claude/` go to the dedicated PVC. Writes to other paths under `{prod-home}/` go to the persistent home PVC. + +If you only need persistence within a single workspace and not cross-workspace sharing, `persistUserHome` alone is sufficient. + +== Limitations + +* Requires manual PVC setup per user {orch-namespace}. +* RWX storage may not be available on all clusters. +* Changes must be synced back to the PVC manually before stopping the workspace. Unsaved changes are lost. + +.Additional resources + +* xref:persist-claude-code-configuration.adoc[] +* xref:share-claude-code-configuration-with-pvc-direct-mount.adoc[] +* xref:share-claude-code-configuration-with-gitops.adoc[] +* xref:troubleshoot-claude-code-configuration-persistence.adoc[] +* xref:requesting-persistent-storage-for-workspaces.adoc[] diff --git a/modules/end-user-guide/pages/troubleshoot-claude-code-configuration-persistence.adoc b/modules/end-user-guide/pages/troubleshoot-claude-code-configuration-persistence.adoc new file mode 100644 index 0000000000..cc06fe2308 --- /dev/null +++ b/modules/end-user-guide/pages/troubleshoot-claude-code-configuration-persistence.adoc @@ -0,0 +1,174 @@ +:_content-type: REFERENCE +:description: Diagnose and resolve issues with persisting Claude Code configuration in {prod-short} workspaces. +:keywords: user-guide, claude-code, ai, configuration, persistence, troubleshooting +:navtitle: Troubleshoot Claude Code configuration persistence +:page-aliases: troubleshooting-claude-code-configuration-persistence.adoc + +[id="troubleshoot-claude-code-configuration-persistence"] += Troubleshoot Claude Code configuration persistence + +If you encounter issues with Claude Code configuration persistence in {prod-short} workspaces, use the following symptoms and solutions to diagnose and resolve them. + +== `.claude.json` corrupted on first start + +=== Symptom + +---- +Claude configuration file at /home/user/.claude.json is corrupted: JSON Parse error: Unexpected EOF +---- + +=== Cause + +The `.claude.json` file was pre-created as an empty file, for example with `touch`, instead of valid JSON. + +=== Solution + +Initialize the file with valid JSON content: + +---- +$ echo '{}' > .claude.json +---- + +If you already see this error, select *Reset with default configuration* when prompted. This is a one-time prompt that does not recur. + +== `.claude.json` mounted as a directory + +=== Symptom + +Claude Code fails to start or reports that `{prod-home}/.claude.json` is not a valid file. Inspecting the mount shows a directory instead of a file: + +---- +$ stat {prod-home}/.claude.json + File: {prod-home}/.claude.json + Size: 4096 Blocks: 8 IO Block: 4096 directory +---- + +=== Cause + +When a PVC `subPath` mount target does not exist on the volume, {kubernetes} kubelet creates it as a *directory*, not a file. The mount point is locked and cannot be replaced from inside the container. + +=== Solution + +Do not mount `{prod-home}/.claude.json` as a direct subPath from a PVC without pre-creating the file. Either use an init pod to pre-create it (xref:share-claude-code-configuration-with-pvc-direct-mount.adoc[direct mount]) or use the xref:share-claude-code-configuration-with-pvc-tmp-copy.adoc[tmp copy] approach. + +== Permission denied writing to `~/.claude/session-env` + +=== Symptom + +---- +The workspace environment blocks write access to /home/user/.claude/session-env +---- + +=== Cause + +On OpenShift, the Security Context Constraints (SCC) assign a random UID per {orch-namespace}. If the PVC contents were created with a different UID or group, write access fails. + +=== Solution + +On OpenShift, PVC filesystems typically receive a setgid bit (`drwxrwsr-x`) with group `0`, and workspace containers run with GID `0`. When both conditions are met, writes succeed without manual permission changes. + +If you encounter this error, verify that: + +* The PVC was created in the same {orch-namespace} as the workspace. +* The PVC contents have group `0` ownership: ++ +---- +$ stat {prod-home}/.claude +---- ++ +If the group is not `0` (root), the PVC contents were likely created by a process with a different GID. Regular workspace users cannot fix this with `chgrp` because OpenShift assigns a random UID without permission to change file groups. Delete the PVC, recreate it, and let a workspace pod be the first consumer. Files created by the workspace process will have the correct group ownership. + +== Multi-attach error when starting a second workspace + +=== Symptom + +---- +Multi-Attach error for volume "pvc-xxx": Volume is already exclusively attached to one node +---- + +=== Cause + +The PVC uses `ReadWriteOnce` (RWO) access mode. RWO volumes can only attach to a single node at a time. When two workspace pods are scheduled on different nodes, the second pod cannot mount the volume. + +=== Solution + +Choose one of the following: + +* Run only one workspace at a time. +* Use `ReadWriteMany` (RWX) access mode with a storage class that supports it, such as AWS EFS or NFS. Standard block storage classes (gp2, gp3) do not support RWX. +* Use `controller.devfile.io/mount-to-devworkspace-include` or `controller.devfile.io/mount-to-devworkspace-exclude` annotations on the PVC to control which workspaces mount it. See xref:share-claude-code-configuration-with-pvc-direct-mount.adoc#_filtering_by_workspace_name[Filtering by workspace name]. + +To check available storage classes: + +[subs="+quotes,+attributes"] +---- +$ {orch-cli} get storageclasses +---- + +Look for classes using EFS (`efs.csi.aws.com`) or NFS provisioners. + +== Changes to `~/.claude.json` not persisting after restart + +=== Symptom + +After restarting a workspace, MCP server configurations added during the previous session are lost. + +=== Cause + +This applies to the xref:share-claude-code-configuration-with-pvc-tmp-copy.adoc[tmp copy] approach. The `postStart` command copies configurations from the PVC into the home directory. Changes made during the session are written to the home directory, not back to the PVC. + +The xref:share-claude-code-configuration-with-pvc-direct-mount.adoc[direct mount] approach does not have this issue because changes are written directly to the PVC. + +=== Solution + +Sync changes back to the PVC before stopping the workspace: + +[subs="+quotes,+attributes"] +---- +$ cp -a {prod-home}/.claude/. /tmp/claude/.claude/ && \ + cp {prod-home}/.claude.json /tmp/claude/.claude.json +---- + +[id="pvc-scheduling-failure-after-init-pod-setup"] +== PVC scheduling failure after init pod setup + +=== Symptom + +---- +0/N nodes are available: X node(s) didn't match PersistentVolume's node affinity +---- + +A workspace fails to start after a PVC was initialized using a temporary pod. + +=== Cause + +On multi-AZ clusters using RWO block storage (gp2, gp3), the PersistentVolume is provisioned in a single availability zone. + +This issue does not affect single-AZ clusters or clusters using RWX storage (EFS, NFS). + +=== Solution + +Do not use a separate init pod to initialize PVC contents on clusters with `WaitForFirstConsumer` storage classes. Instead: + +. Let the workspace pod be the first consumer of the PVC. +. Handle file initialization with `postStart` commands from inside the workspace. + +If you already have a PVC stuck in the wrong availability zone: + +. Stop all workspaces using the PVC. +. Delete the PVC: ++ +[subs="+quotes,+attributes"] +---- +$ {orch-cli} delete pvc ____ -n __<{orch-namespace}>__ +---- +. Recreate the PVC and start a workspace. The PV is provisioned in the correct zone. + +For clusters where this is a recurring issue, use the xref:share-claude-code-configuration-with-pvc-tmp-copy.adoc[tmp copy] approach, which does not require an init pod. + +.Additional resources + +* xref:persist-claude-code-configuration.adoc[] +* xref:share-claude-code-configuration-with-pvc-direct-mount.adoc[] +* xref:share-claude-code-configuration-with-pvc-tmp-copy.adoc[] +* xref:share-claude-code-configuration-with-gitops.adoc[]