fix(worktrees): the injected agent skill no longer blocks removal

With Codeman Agent Skill on (agentSkillEnabled), every session create
writes the skill into the folder the session runs in, so a session in a
worktree leaves an untracked .claude/skills/codeman/ (claude) or
.agents/skills/codeman/ (codex, gemini and the rest) behind. git then
refuses `git worktree remove` for that worktree forever, and an agent
running `git add -A` there would commit Codeman's skill into its branch
(reproduced: the worktree's status showed "?? .agents/" and
"?? .claude/").

A create now excludes <skills dir>/codeman/ next to the hooks file, for
every skills folder the registry names (capabilities.agentSkillDir,
read at call time by the route and passed in as data, so no CLI id is
branched on). Like the other lines it is only added when git does not
already ignore the path, and only to the repository's local
info/exclude.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-10-14 05:37:48 +02:00
1 parent 64fea4ff84
commit 28cbbad591
7 files changed
+90 -8

No files matched your search

+19 -1
View File
@@ -29,6 +29,8 @@ import { readDockerCases } from '../../docker-hosts.js';
import { readRemoteCases } from '../../remote-hosts.js';
import { dataPath, getDataDir } from '../../config/instance.js';
import { isMultiUserMode } from '../../config/multiuser.js';
import { listClis } from '../../config/cli-registry/registry.js';
import { agentSkillDir } from '../../hooks-config.js';
import { describeUnknownPath, probePathKind } from '../../utils/index.js';
import {
createCaseWorktree,
@@ -65,6 +67,19 @@ const SAFE_CASE_NAME = /^[a-zA-Z0-9_-]+$/;
const defaultDockerWorkspaces = async (): Promise<string[]> =>
(await readDockerCases(getDataDir()).catch(() => [])).map((c) => c.hostWorkspacePath).filter(Boolean);
/**
* Every workspace skills folder a session of some registry CLI gets Codeman's agent skill in
* (`capabilities.agentSkillDir`), read from the registry at call time as data, never by CLI id.
*/
const registryAgentSkillDirs = (): string[] => {
const dirs = new Set<string>();
for (const cli of listClis()) {
const dir = agentSkillDir(cli.capabilities);
if (dir) dirs.add(dir);
}
return [...dirs];
};
/** A live session in a worktree, as the list reports it to a caller who may see it. */
export interface WorktreeSessionRef {
id: string;
@@ -239,7 +254,10 @@ export function registerWorktreeRoutes(
if (nameError) return failWith(reply, ApiErrorCode.INVALID_INPUT, nameError);
const resolved = await caseRepository(req, reply, name);
if (!resolved.ok) return resolved.fail;
const created = await createCaseWorktree(resolved.repo, body, { git: deps.git });
const created = await createCaseWorktree(resolved.repo, body, {
git: deps.git,
agentSkillDirs: registryAgentSkillDirs(),
});
if (!created.ok) return failFrom(reply, created);
reply.code(201);
return { success: true, data: { worktree: created.worktree } };