Skip to content

Commit b7546a7

Browse files
Remote Agentclaude
andcommitted
fix: copy CLAUDE.md and skills from templates dir instead of volume-mounted config
/app/config is volume-mounted by docker-compose, which overwrites the Dockerfile COPY. Move templates to /app/templates/claude/ and add deployGlobalTemplates() that copies both CLAUDE.md and skills directory to workspaces. Also deploy templates on terminal creation, not just pairWorkspace. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5597226 commit b7546a7

3 files changed

Lines changed: 27 additions & 35 deletions

File tree

docker/Dockerfile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,8 @@ RUN mkdir -p /app/workspaces /app/ssh-keys /app/config/hooks /app/config/skills
147147
# Copy hook scripts (--chmod eliminates ghost layer from separate chmod)
148148
COPY --chown=agent:agent --chmod=755 docker/hooks/ /app/hooks/
149149

150-
# Copy global CLAUDE.md template
151-
COPY --chown=agent:agent docker/claude/ /app/config/claude/
150+
# Copy global CLAUDE.md + skills templates (to /app/templates, NOT /app/config which is volume-mounted)
151+
COPY --chown=agent:agent docker/claude/ /app/templates/claude/
152152

153153
# ── Metadata & environment ──
154154
ENV APP_VERSION=$VERSION
@@ -157,6 +157,7 @@ ENV BUN_TMPDIR=/tmp
157157
ENV WORKSPACES_ROOT=/app/workspaces
158158
ENV SSH_KEYS_ROOT=/app/ssh-keys
159159
ENV CONFIG_ROOT=/app/config
160+
ENV TEMPLATES_ROOT=/app/templates
160161
ENV NVM_DIR=/home/agent/.nvm
161162
ENV PATH="/home/agent/.local/bin:/home/agent/.nvm/default_bin:/usr/local/bin:${PATH}"
162163

packages/api/src/routes/terminals.routes.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,9 @@ export const terminalRoutes = new Elysia({ prefix: '/terminals' })
8282
}
8383
name = body.name || 'Claude';
8484

85-
// Ensure user workspace and .claude directory exist with default hooks
85+
// Ensure user workspace, .claude directory, templates, and hooks exist
8686
await workspaceService.createUserWorkspace(user!.id);
87+
await workspaceService.deployGlobalTemplates(user!.id);
8788
await workspaceService.storeHooks(user!.id, body.sessionId, terminalId);
8889

8990
// Set session and terminal IDs for hook callbacks

packages/api/src/services/workspace/workspace.service.ts

Lines changed: 22 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,13 @@ export class WorkspaceService {
3737
private workspacesRoot: string;
3838
private sshKeysRoot: string;
3939
private configRoot: string;
40+
private templatesRoot: string;
4041

41-
constructor(opts?: { workspacesRoot?: string; sshKeysRoot?: string; configRoot?: string }) {
42+
constructor(opts?: { workspacesRoot?: string; sshKeysRoot?: string; configRoot?: string; templatesRoot?: string }) {
4243
this.workspacesRoot = opts?.workspacesRoot || '/app/workspaces';
4344
this.sshKeysRoot = opts?.sshKeysRoot || '/app/ssh-keys';
4445
this.configRoot = opts?.configRoot || '/app/config';
46+
this.templatesRoot = opts?.templatesRoot || '/app/templates';
4547
}
4648

4749
async createUserWorkspace(userId: string): Promise<string> {
@@ -59,21 +61,18 @@ export class WorkspaceService {
5961
await this.storeSSHKey(userId, opts.sshPrivateKey, opts.sshPublicKey);
6062
}
6163

62-
// 2. Deploy config-based skills (from /app/config/claude/skills/)
63-
await this.deploySkills(userId);
64+
// 2. Deploy global CLAUDE.md + skills from templates
65+
await this.deployGlobalTemplates(userId);
6466

65-
// 3. Store custom API-provided skills (on top of config-based ones)
67+
// 3. Store custom API-provided skills (on top of template ones)
6668
if (opts.skills?.length) {
6769
await this.storeSkills(userId, opts.skills);
6870
}
6971

70-
// 4. Deploy global CLAUDE.md
71-
await this.deployCLAUDEmd(userId);
72-
73-
// 5. Store custom hooks (merged with notification hooks)
72+
// 4. Store custom hooks (merged with notification hooks)
7473
await this.storeHooks(userId, undefined, undefined, opts.hooks);
7574

76-
// 6. Store Claude settings
75+
// 5. Store Claude settings
7776
if (opts.claudeSettings) {
7877
await this.storeSettings(userId, opts.claudeSettings);
7978
}
@@ -162,35 +161,25 @@ export class WorkspaceService {
162161
}
163162
}
164163

165-
async deployCLAUDEmd(userId: string): Promise<void> {
166-
const sourcePath = join(this.configRoot, 'claude', 'CLAUDE.md');
167-
const destPath = join(this.workspacesRoot, userId, 'CLAUDE.md');
164+
async deployGlobalTemplates(userId: string): Promise<void> {
165+
const claudeTemplateDir = join(this.templatesRoot, 'claude');
166+
const userPath = join(this.workspacesRoot, userId);
167+
168+
// Deploy CLAUDE.md to workspace root
168169
try {
169-
await copyFile(sourcePath, destPath);
170+
await copyFile(join(claudeTemplateDir, 'CLAUDE.md'), join(userPath, 'CLAUDE.md'));
170171
} catch {
171-
// CLAUDE.md template not found, skip silently
172+
// CLAUDE.md template not found, skip
172173
}
173-
}
174-
175-
async deploySkills(userId: string): Promise<void> {
176-
const sourceDir = join(this.configRoot, 'claude', 'skills');
177-
const destDir = join(this.workspacesRoot, userId, '.claude', 'skills');
178174

175+
// Deploy skills to .claude/skills/
179176
try {
180-
const entries = await readdir(sourceDir, { withFileTypes: true });
181-
const skillDirs = entries.filter(e => e.isDirectory() && !e.name.startsWith('.'));
182-
183-
if (skillDirs.length === 0) return;
184-
185-
await mkdir(destDir, { recursive: true });
186-
187-
for (const dir of skillDirs) {
188-
const src = join(sourceDir, dir.name);
189-
const dest = join(destDir, dir.name);
190-
await cp(src, dest, { recursive: true, force: true });
191-
}
177+
const skillsSource = join(claudeTemplateDir, 'skills');
178+
const skillsDest = join(userPath, '.claude', 'skills');
179+
await readdir(skillsSource);
180+
await cp(skillsSource, skillsDest, { recursive: true, force: true });
192181
} catch {
193-
// Skills directory not found, skip silently
182+
// Skills directory not found, skip
194183
}
195184
}
196185

@@ -335,4 +324,5 @@ export const workspaceService = new WorkspaceService({
335324
workspacesRoot: process.env.WORKSPACES_ROOT || '/app/workspaces',
336325
sshKeysRoot: process.env.SSH_KEYS_ROOT || '/app/ssh-keys',
337326
configRoot: process.env.CONFIG_ROOT || '/app/config',
327+
templatesRoot: process.env.TEMPLATES_ROOT || '/app/templates',
338328
});

0 commit comments

Comments
 (0)