Move session root to ~/.agentworkforce/workforce/sessions - #60

Merged
willwashburn merged 1 commit into
mainfrom
worktree-fix-mount-path
May 8, 2026
Merged

Move session root to ~/.agentworkforce/workforce/sessions#60
willwashburn merged 1 commit into
mainfrom
worktree-fix-mount-path

Conversation

@willwashburn

Copy link
Copy Markdown
Member

Summary

  • Update generateSessionRoot in packages/cli/src/cli.ts so the per-session staging directory lives at ~/.agentworkforce/workforce/sessions/<id>/ instead of ~/.agent-workforce/sessions/<id>/. This aligns the mount path (<root>/mount/) and skill-stage path (<root>/claude/plugin/) with the rest of the project's home-dir layout (~/.agentworkforce/workforce/personas, ~/.agentworkforce/workforce/config.json).
  • Refresh the surrounding doc comments in packages/cli/src/cli.ts and packages/workload-router/src/index.ts, plus the layout examples in README.md and packages/cli/README.md, so the documented paths match the new behavior.

Test plan

  • pnpm -r build
  • pnpm -r --filter @agentworkforce/cli --filter @agentworkforce/workload-router typecheck
  • pnpm -r --filter @agentworkforce/cli --filter @agentworkforce/workload-router test (129 cli tests, workload-router tests pass)
  • Smoke-test an interactive agentworkforce agent run and confirm ~/.agentworkforce/workforce/sessions/<id>/mount/ is created and removed on exit

🤖 Generated with Claude Code

Align the CLI's interactive-session staging directory with the rest of
the project's home-dir conventions (~/.agentworkforce/workforce/personas,
~/.agentworkforce/workforce/config.json) so the mount path lands at
~/.agentworkforce/workforce/sessions/<id>/mount/ instead of
~/.agent-workforce/sessions/<id>/mount/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR migrates the interactive session and skill staging root directory from ~/.agent-workforce/ to ~/.agentworkforce/workforce/. The session path generation logic in the CLI is updated, and all supporting documentation across both root and packages READMEs is synchronized to reflect this structural change.

Changes

Home Directory Path Migration

Layer / File(s)Summary
Path Generation Logic
packages/cli/src/cli.ts
generateSessionRoot() now constructs session directories under ~/.agentworkforce/workforce/sessions/ instead of ~/.agent-workforce/sessions/. Session cleanup comment updated to match.
Implementation Documentation
packages/workload-router/src/index.ts
SkillMaterializationOptions doc comment path example updated from ~/.agent-workforce/... to ~/.agentworkforce/....
User-Facing Documentation
README.md, packages/cli/README.md
All references to the home directory and session/stage paths updated across skill staging sections, sandbox mount documentation, session layout diagrams, cache directory references, and plugin directory paths.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Possibly related PRs

  • AgentWorkforce/workforce#35: Touches the same CLI session filesystem paths and updates packages/cli/src/cli.ts for the ~/.agent-workforce to ~/.agentworkforce/workforce migration.
  • AgentWorkforce/workforce#50: Updates the same home directory layout migration, changing CLI and README code that constructs or documents the new ~/.agentworkforce/workforce/ paths.

Poem

🐰 Paths refactored with careful grace,
From agent-workforce to its new place,
Through directories neat and directories deep,
These sessions and stages their promises keep!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 66.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly and specifically describes the main change—moving the session root directory to a new path.
Description check✅ PassedThe description directly addresses the changeset, detailing the path migration and documentation updates across multiple files.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-fix-mount-path

Comment @coderabbitai help to get the list of available commands and usage tips.

@devin-ai-integrationdevin-ai-integrationBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/cli.ts`:
- Around line 529-531: The generateSessionRoot function uses personaId directly
in a filesystem path which allows path-traversal; sanitize personaId before
constructing the path (in generateSessionRoot) by stripping or replacing path
separators and any ".." sequences (e.g., collapse to a safe token via
path.basename, a strict allowlist, or url-safe encoding) and reject or normalize
empty/invalid values; after building the path verify it is confined inside the
intended sessions directory (resolve and ensure it startsWith the sessions root)
and fail rather than return a path outside, and make the same validation
expectation clear for any caller like removeSessionRoot that performs recursive
rmSync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ab41c5a1-d4e5-4f43-a1b4-14b46775503e

📥 Commits

Reviewing files that changed from the base of the PR and between a1b5d92 and ff0ddfa.

📒 Files selected for processing (4)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/cli.ts
  • packages/workload-router/src/index.ts

Comment on lines 529 to +531
function generateSessionRoot(personaId: string): string {
const sessionId = `${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
return join(homedir(), '.agent-workforce', 'sessions', `${personaId}-${sessionId}`);
return join(homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== Session root construction and cleanup =="
rg -n "function generateSessionRoot|function removeSessionRoot|rmSync\\(sessionRoot" packages/cli/src/cli.ts -C2
echoecho"== Persona ID validation rules =="
rg -n "persona\\[\\$\\{expectedIntent\\}\\]\\.id|id must be a non-empty string" packages/workload-router/src/index.ts -C2
echoecho"== Check for strict persona-id regex validation =="
rg -n "persona.*id.*regex|id.*match\\(|assert.*id|INPUT_NAME_RE|SKILL_NAME_RE" packages/workload-router/src/index.ts -C2

Repository: AgentWorkforce/workforce

Length of output: 3399


🏁 Script executed:

# Find where generateSessionRoot is called
rg -n "generateSessionRoot" packages/cli/src/cli.ts -B2 -A2

Repository: AgentWorkforce/workforce

Length of output: 683


🏁 Script executed:

# Check how personaId is obtained/validated before being used
rg -n "personaId|persona.*id" packages/cli/src/cli.ts | head -50

Repository: AgentWorkforce/workforce

Length of output: 2344


🏁 Script executed:

# Check Node.js path.join behavior - understand the actual impact
node <<'EOF'const path = require('path');const os = require('os');// Test cases for path.join with potential traversal attemptsconst testCases = [ "normal-persona", "../../../tmp", "../../tmp", "/etc/passwd", "persona/../../tmp", "..%2F..%2Ftmp"];console.log("Testing path.join() behavior:");testCases.forEach(personaId => { const sessionId = "abc123"; const result = path.join( os.homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}` ); console.log(`\nPersonaId: "${personaId}"`); console.log(`Result: ${result}`);});EOF

Repository: AgentWorkforce/workforce

Length of output: 643


🏁 Script executed:

# Check CLI parsing for personaId to see if there's any validation
rg -n "spec\.id|spec = |--persona" packages/cli/src/cli.ts -B3 -A3 | head -80

Repository: AgentWorkforce/workforce

Length of output: 3104


🏁 Script executed:

# Check what spec object is and where it comes from
rg -n "const spec|interface.*Spec|type.*Spec" packages/cli/src/cli.ts | head -20

Repository: AgentWorkforce/workforce

Length of output: 360


🏁 Script executed:

# Check if there's any validation between CLI parsing and generateSessionRoot usage
rg -n "effectiveSelection|getEffectiveSelection" packages/cli/src/cli.ts -B2 -A5

Repository: AgentWorkforce/workforce

Length of output: 6379


🏁 Script executed:

# Trace the flow from CLI argument parsing to generateSessionRoot# Check runPersonaAgent function to see complete flow
rg -n "runPersonaAgent|function runPersonaAgent" packages/cli/src/cli.ts -A100 | head -150

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Check the agent command implementation
rg -n "function.*agent\(|case 'agent'" packages/cli/src/cli.ts -A20 | head -100

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Search for where personaId gets picked/selected before being used
rg -n "pick|select|findPersona|getPersona" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 1977


🏁 Script executed:

# Find the main dispatch for CLI commands to understand flow better
rg -n "if.*command|switch.*command|case" packages/cli/src/cli.ts | head -40

Repository: AgentWorkforce/workforce

Length of output: 673


🏁 Script executed:

# Look for where personaId comes from in the agent flow - search backwards from generateSessionRoot
sed -n '1095,1100p' packages/cli/src/cli.ts

Repository: AgentWorkforce/workforce

Length of output: 379


🏁 Script executed:

# Find where the selection object is created that has personaId
rg -n "PersonaSelection|selection.*=.*{" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 196


🏁 Script executed:

# Find the agent command main entry point
rg -n "subcommand === 'agent'" packages/cli/src/cli.ts -A50 | head -80

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Check what happens with the agent personaId - trace the flow
sed -n '2571,2600p' packages/cli/src/cli.ts

Repository: AgentWorkforce/workforce

Length of output: 841


🏁 Script executed:

# Check the parseAgentArg or similar function
rg -n "parseAgentArg|parsePersonaId|function.*agent" packages/cli/src/cli.ts -B2 -A10 | head -60

Repository: AgentWorkforce/workforce

Length of output: 858


🏁 Script executed:

# Find runAgentSelector function
rg -n "function runAgentSelector|runAgentSelector.*=" packages/cli/src/cli.ts -A50 | head -100

Repository: AgentWorkforce/workforce

Length of output: 1716


🏁 Script executed:

# Check how selector is parsed - likely has @ symbol for tier selection
rg -n "selector|@" packages/cli/src/cli.ts | grep -E "(selector|@)"| head -40

Repository: AgentWorkforce/workforce

Length of output: 2411


🏁 Script executed:

# Find parseSelector function
rg -n "function parseSelector" packages/cli/src/cli.ts -A30

Repository: AgentWorkforce/workforce

Length of output: 1671


🏁 Script executed:

# Check if persona IDs loaded from specs have any validation
rg -n "listBuiltInPersonas|local.byId|spec.id" packages/cli/src/cli.ts | head -20

Repository: AgentWorkforce/workforce

Length of output: 979


🏁 Script executed:

# Trace where persona specs are loaded/validated - check if there's validation
rg -n "byId|buildSelection" packages/cli/src/cli.ts -B5 -A5 | head -60

Repository: AgentWorkforce/workforce

Length of output: 2637


🏁 Script executed:

# Check where local personas are loaded/stored and if they're validated
rg -n "local\\.byId|loadLocal|personas.json|PersonaSpec" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 1321


🏁 Script executed:

# Check the PersonaSpec type definition - see if id field is validated
rg -n "type PersonaSpec|interface PersonaSpec" packages/ -B2 -A15 | head -50

Repository: AgentWorkforce/workforce

Length of output: 3549


🏁 Script executed:

# Check if there are any validations on persona IDs when they're loaded/parsed
rg -n "persona.*id|validatePersona|validateId" packages/ | grep -v node_modules | head -30

Repository: AgentWorkforce/workforce

Length of output: 18165


Sanitize personaId before building sessionRoot path.

At line 531, personaId is used directly in the filesystem path without sanitization. Because personaId comes from user input (CLI --persona flag and agentworkforce agent <id> commands), malicious values containing path separators or .. sequences escape the .../sessions/ directory. Combined with removeSessionRoot's recursive rmSync, this enables deletion of arbitrary directories outside the intended session folder.

🔧 Proposed fix
 function generateSessionRoot(personaId: string): string {
+ const safePersonaId = personaId.replace(/[^A-Za-z0-9._-]/g, '_');
const sessionId = `${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
- return join(homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}`);+ return join(+ homedir(),+ '.agentworkforce',+ 'workforce',+ 'sessions',+ `${safePersonaId}-${sessionId}`+ );
}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
functiongenerateSessionRoot(personaId: string): string{
constsessionId=`${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
returnjoin(homedir(),'.agent-workforce','sessions',`${personaId}-${sessionId}`);
returnjoin(homedir(),'.agentworkforce','workforce','sessions',`${personaId}-${sessionId}`);
functiongenerateSessionRoot(personaId: string): string{
constsafePersonaId=personaId.replace(/[^A-Za-z0-9._-]/g,'_');
constsessionId=`${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
returnjoin(
homedir(),
'.agentworkforce',
'workforce',
'sessions',
`${safePersonaId}-${sessionId}`
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/cli.ts` around lines 529 - 531, The generateSessionRoot
function uses personaId directly in a filesystem path which allows
path-traversal; sanitize personaId before constructing the path (in
generateSessionRoot) by stripping or replacing path separators and any ".."
sequences (e.g., collapse to a safe token via path.basename, a strict allowlist,
or url-safe encoding) and reject or normalize empty/invalid values; after
building the path verify it is confined inside the intended sessions directory
(resolve and ensure it startsWith the sessions root) and fail rather than return
a path outside, and make the same validation expectation clear for any caller
like removeSessionRoot that performs recursive rmSync.

@willwashburn
willwashburn merged commit 3c97a11 into mainMay 8, 2026
3 checks passed
@willwashburn
willwashburn deleted the worktree-fix-mount-path branch May 8, 2026 17:37
Sign up for freeto 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.

1 participant

@willwashburn
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Move session root to ~/.agentworkforce/workforce/sessions - #60

Merged
willwashburn merged 1 commit into
mainfrom
worktree-fix-mount-path
May 8, 2026
Merged

Move session root to ~/.agentworkforce/workforce/sessions#60
willwashburn merged 1 commit into
mainfrom
worktree-fix-mount-path

Conversation

@willwashburn

Copy link
Copy Markdown
Member

Summary

  • Update generateSessionRoot in packages/cli/src/cli.ts so the per-session staging directory lives at ~/.agentworkforce/workforce/sessions/<id>/ instead of ~/.agent-workforce/sessions/<id>/. This aligns the mount path (<root>/mount/) and skill-stage path (<root>/claude/plugin/) with the rest of the project's home-dir layout (~/.agentworkforce/workforce/personas, ~/.agentworkforce/workforce/config.json).
  • Refresh the surrounding doc comments in packages/cli/src/cli.ts and packages/workload-router/src/index.ts, plus the layout examples in README.md and packages/cli/README.md, so the documented paths match the new behavior.

Test plan

  • pnpm -r build
  • pnpm -r --filter @agentworkforce/cli --filter @agentworkforce/workload-router typecheck
  • pnpm -r --filter @agentworkforce/cli --filter @agentworkforce/workload-router test (129 cli tests, workload-router tests pass)
  • Smoke-test an interactive agentworkforce agent run and confirm ~/.agentworkforce/workforce/sessions/<id>/mount/ is created and removed on exit

🤖 Generated with Claude Code

Align the CLI's interactive-session staging directory with the rest of
the project's home-dir conventions (~/.agentworkforce/workforce/personas,
~/.agentworkforce/workforce/config.json) so the mount path lands at
~/.agentworkforce/workforce/sessions/<id>/mount/ instead of
~/.agent-workforce/sessions/<id>/mount/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR migrates the interactive session and skill staging root directory from ~/.agent-workforce/ to ~/.agentworkforce/workforce/. The session path generation logic in the CLI is updated, and all supporting documentation across both root and packages READMEs is synchronized to reflect this structural change.

Changes

Home Directory Path Migration

Layer / File(s)Summary
Path Generation Logic
packages/cli/src/cli.ts
generateSessionRoot() now constructs session directories under ~/.agentworkforce/workforce/sessions/ instead of ~/.agent-workforce/sessions/. Session cleanup comment updated to match.
Implementation Documentation
packages/workload-router/src/index.ts
SkillMaterializationOptions doc comment path example updated from ~/.agent-workforce/... to ~/.agentworkforce/....
User-Facing Documentation
README.md, packages/cli/README.md
All references to the home directory and session/stage paths updated across skill staging sections, sandbox mount documentation, session layout diagrams, cache directory references, and plugin directory paths.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Possibly related PRs

  • AgentWorkforce/workforce#35: Touches the same CLI session filesystem paths and updates packages/cli/src/cli.ts for the ~/.agent-workforce to ~/.agentworkforce/workforce migration.
  • AgentWorkforce/workforce#50: Updates the same home directory layout migration, changing CLI and README code that constructs or documents the new ~/.agentworkforce/workforce/ paths.

Poem

🐰 Paths refactored with careful grace,
From agent-workforce to its new place,
Through directories neat and directories deep,
These sessions and stages their promises keep!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 66.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly and specifically describes the main change—moving the session root directory to a new path.
Description check✅ PassedThe description directly addresses the changeset, detailing the path migration and documentation updates across multiple files.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-fix-mount-path

Comment @coderabbitai help to get the list of available commands and usage tips.

@devin-ai-integrationdevin-ai-integrationBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/cli.ts`:
- Around line 529-531: The generateSessionRoot function uses personaId directly
in a filesystem path which allows path-traversal; sanitize personaId before
constructing the path (in generateSessionRoot) by stripping or replacing path
separators and any ".." sequences (e.g., collapse to a safe token via
path.basename, a strict allowlist, or url-safe encoding) and reject or normalize
empty/invalid values; after building the path verify it is confined inside the
intended sessions directory (resolve and ensure it startsWith the sessions root)
and fail rather than return a path outside, and make the same validation
expectation clear for any caller like removeSessionRoot that performs recursive
rmSync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ab41c5a1-d4e5-4f43-a1b4-14b46775503e

📥 Commits

Reviewing files that changed from the base of the PR and between a1b5d92 and ff0ddfa.

📒 Files selected for processing (4)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/cli.ts
  • packages/workload-router/src/index.ts

Comment on lines 529 to +531
function generateSessionRoot(personaId: string): string {
const sessionId = `${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
return join(homedir(), '.agent-workforce', 'sessions', `${personaId}-${sessionId}`);
return join(homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== Session root construction and cleanup =="
rg -n "function generateSessionRoot|function removeSessionRoot|rmSync\\(sessionRoot" packages/cli/src/cli.ts -C2
echoecho"== Persona ID validation rules =="
rg -n "persona\\[\\$\\{expectedIntent\\}\\]\\.id|id must be a non-empty string" packages/workload-router/src/index.ts -C2
echoecho"== Check for strict persona-id regex validation =="
rg -n "persona.*id.*regex|id.*match\\(|assert.*id|INPUT_NAME_RE|SKILL_NAME_RE" packages/workload-router/src/index.ts -C2

Repository: AgentWorkforce/workforce

Length of output: 3399


🏁 Script executed:

# Find where generateSessionRoot is called
rg -n "generateSessionRoot" packages/cli/src/cli.ts -B2 -A2

Repository: AgentWorkforce/workforce

Length of output: 683


🏁 Script executed:

# Check how personaId is obtained/validated before being used
rg -n "personaId|persona.*id" packages/cli/src/cli.ts | head -50

Repository: AgentWorkforce/workforce

Length of output: 2344


🏁 Script executed:

# Check Node.js path.join behavior - understand the actual impact
node <<'EOF'const path = require('path');const os = require('os');// Test cases for path.join with potential traversal attemptsconst testCases = [ "normal-persona", "../../../tmp", "../../tmp", "/etc/passwd", "persona/../../tmp", "..%2F..%2Ftmp"];console.log("Testing path.join() behavior:");testCases.forEach(personaId => { const sessionId = "abc123"; const result = path.join( os.homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}` ); console.log(`\nPersonaId: "${personaId}"`); console.log(`Result: ${result}`);});EOF

Repository: AgentWorkforce/workforce

Length of output: 643


🏁 Script executed:

# Check CLI parsing for personaId to see if there's any validation
rg -n "spec\.id|spec = |--persona" packages/cli/src/cli.ts -B3 -A3 | head -80

Repository: AgentWorkforce/workforce

Length of output: 3104


🏁 Script executed:

# Check what spec object is and where it comes from
rg -n "const spec|interface.*Spec|type.*Spec" packages/cli/src/cli.ts | head -20

Repository: AgentWorkforce/workforce

Length of output: 360


🏁 Script executed:

# Check if there's any validation between CLI parsing and generateSessionRoot usage
rg -n "effectiveSelection|getEffectiveSelection" packages/cli/src/cli.ts -B2 -A5

Repository: AgentWorkforce/workforce

Length of output: 6379


🏁 Script executed:

# Trace the flow from CLI argument parsing to generateSessionRoot# Check runPersonaAgent function to see complete flow
rg -n "runPersonaAgent|function runPersonaAgent" packages/cli/src/cli.ts -A100 | head -150

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Check the agent command implementation
rg -n "function.*agent\(|case 'agent'" packages/cli/src/cli.ts -A20 | head -100

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Search for where personaId gets picked/selected before being used
rg -n "pick|select|findPersona|getPersona" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 1977


🏁 Script executed:

# Find the main dispatch for CLI commands to understand flow better
rg -n "if.*command|switch.*command|case" packages/cli/src/cli.ts | head -40

Repository: AgentWorkforce/workforce

Length of output: 673


🏁 Script executed:

# Look for where personaId comes from in the agent flow - search backwards from generateSessionRoot
sed -n '1095,1100p' packages/cli/src/cli.ts

Repository: AgentWorkforce/workforce

Length of output: 379


🏁 Script executed:

# Find where the selection object is created that has personaId
rg -n "PersonaSelection|selection.*=.*{" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 196


🏁 Script executed:

# Find the agent command main entry point
rg -n "subcommand === 'agent'" packages/cli/src/cli.ts -A50 | head -80

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Check what happens with the agent personaId - trace the flow
sed -n '2571,2600p' packages/cli/src/cli.ts

Repository: AgentWorkforce/workforce

Length of output: 841


🏁 Script executed:

# Check the parseAgentArg or similar function
rg -n "parseAgentArg|parsePersonaId|function.*agent" packages/cli/src/cli.ts -B2 -A10 | head -60

Repository: AgentWorkforce/workforce

Length of output: 858


🏁 Script executed:

# Find runAgentSelector function
rg -n "function runAgentSelector|runAgentSelector.*=" packages/cli/src/cli.ts -A50 | head -100

Repository: AgentWorkforce/workforce

Length of output: 1716


🏁 Script executed:

# Check how selector is parsed - likely has @ symbol for tier selection
rg -n "selector|@" packages/cli/src/cli.ts | grep -E "(selector|@)"| head -40

Repository: AgentWorkforce/workforce

Length of output: 2411


🏁 Script executed:

# Find parseSelector function
rg -n "function parseSelector" packages/cli/src/cli.ts -A30

Repository: AgentWorkforce/workforce

Length of output: 1671


🏁 Script executed:

# Check if persona IDs loaded from specs have any validation
rg -n "listBuiltInPersonas|local.byId|spec.id" packages/cli/src/cli.ts | head -20

Repository: AgentWorkforce/workforce

Length of output: 979


🏁 Script executed:

# Trace where persona specs are loaded/validated - check if there's validation
rg -n "byId|buildSelection" packages/cli/src/cli.ts -B5 -A5 | head -60

Repository: AgentWorkforce/workforce

Length of output: 2637


🏁 Script executed:

# Check where local personas are loaded/stored and if they're validated
rg -n "local\\.byId|loadLocal|personas.json|PersonaSpec" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 1321


🏁 Script executed:

# Check the PersonaSpec type definition - see if id field is validated
rg -n "type PersonaSpec|interface PersonaSpec" packages/ -B2 -A15 | head -50

Repository: AgentWorkforce/workforce

Length of output: 3549


🏁 Script executed:

# Check if there are any validations on persona IDs when they're loaded/parsed
rg -n "persona.*id|validatePersona|validateId" packages/ | grep -v node_modules | head -30

Repository: AgentWorkforce/workforce

Length of output: 18165


Sanitize personaId before building sessionRoot path.

At line 531, personaId is used directly in the filesystem path without sanitization. Because personaId comes from user input (CLI --persona flag and agentworkforce agent <id> commands), malicious values containing path separators or .. sequences escape the .../sessions/ directory. Combined with removeSessionRoot's recursive rmSync, this enables deletion of arbitrary directories outside the intended session folder.

🔧 Proposed fix
 function generateSessionRoot(personaId: string): string {
+ const safePersonaId = personaId.replace(/[^A-Za-z0-9._-]/g, '_');
const sessionId = `${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
- return join(homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}`);+ return join(+ homedir(),+ '.agentworkforce',+ 'workforce',+ 'sessions',+ `${safePersonaId}-${sessionId}`+ );
}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
functiongenerateSessionRoot(personaId: string): string{
constsessionId=`${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
returnjoin(homedir(),'.agent-workforce','sessions',`${personaId}-${sessionId}`);
returnjoin(homedir(),'.agentworkforce','workforce','sessions',`${personaId}-${sessionId}`);
functiongenerateSessionRoot(personaId: string): string{
constsafePersonaId=personaId.replace(/[^A-Za-z0-9._-]/g,'_');
constsessionId=`${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
returnjoin(
homedir(),
'.agentworkforce',
'workforce',
'sessions',
`${safePersonaId}-${sessionId}`
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/cli.ts` around lines 529 - 531, The generateSessionRoot
function uses personaId directly in a filesystem path which allows
path-traversal; sanitize personaId before constructing the path (in
generateSessionRoot) by stripping or replacing path separators and any ".."
sequences (e.g., collapse to a safe token via path.basename, a strict allowlist,
or url-safe encoding) and reject or normalize empty/invalid values; after
building the path verify it is confined inside the intended sessions directory
(resolve and ensure it startsWith the sessions root) and fail rather than return
a path outside, and make the same validation expectation clear for any caller
like removeSessionRoot that performs recursive rmSync.

@willwashburn
willwashburn merged commit 3c97a11 into mainMay 8, 2026
3 checks passed
@willwashburn
willwashburn deleted the worktree-fix-mount-path branch May 8, 2026 17:37
Sign up for freeto 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.

1 participant

@willwashburn
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Move session root to ~/.agentworkforce/workforce/sessions - #60

Merged
willwashburn merged 1 commit into
mainfrom
worktree-fix-mount-path
May 8, 2026
Merged

Move session root to ~/.agentworkforce/workforce/sessions#60
willwashburn merged 1 commit into
mainfrom
worktree-fix-mount-path

Conversation

@willwashburn

Copy link
Copy Markdown
Member

Summary

  • Update generateSessionRoot in packages/cli/src/cli.ts so the per-session staging directory lives at ~/.agentworkforce/workforce/sessions/<id>/ instead of ~/.agent-workforce/sessions/<id>/. This aligns the mount path (<root>/mount/) and skill-stage path (<root>/claude/plugin/) with the rest of the project's home-dir layout (~/.agentworkforce/workforce/personas, ~/.agentworkforce/workforce/config.json).
  • Refresh the surrounding doc comments in packages/cli/src/cli.ts and packages/workload-router/src/index.ts, plus the layout examples in README.md and packages/cli/README.md, so the documented paths match the new behavior.

Test plan

  • pnpm -r build
  • pnpm -r --filter @agentworkforce/cli --filter @agentworkforce/workload-router typecheck
  • pnpm -r --filter @agentworkforce/cli --filter @agentworkforce/workload-router test (129 cli tests, workload-router tests pass)
  • Smoke-test an interactive agentworkforce agent run and confirm ~/.agentworkforce/workforce/sessions/<id>/mount/ is created and removed on exit

🤖 Generated with Claude Code

Align the CLI's interactive-session staging directory with the rest of
the project's home-dir conventions (~/.agentworkforce/workforce/personas,
~/.agentworkforce/workforce/config.json) so the mount path lands at
~/.agentworkforce/workforce/sessions/<id>/mount/ instead of
~/.agent-workforce/sessions/<id>/mount/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR migrates the interactive session and skill staging root directory from ~/.agent-workforce/ to ~/.agentworkforce/workforce/. The session path generation logic in the CLI is updated, and all supporting documentation across both root and packages READMEs is synchronized to reflect this structural change.

Changes

Home Directory Path Migration

Layer / File(s)Summary
Path Generation Logic
packages/cli/src/cli.ts
generateSessionRoot() now constructs session directories under ~/.agentworkforce/workforce/sessions/ instead of ~/.agent-workforce/sessions/. Session cleanup comment updated to match.
Implementation Documentation
packages/workload-router/src/index.ts
SkillMaterializationOptions doc comment path example updated from ~/.agent-workforce/... to ~/.agentworkforce/....
User-Facing Documentation
README.md, packages/cli/README.md
All references to the home directory and session/stage paths updated across skill staging sections, sandbox mount documentation, session layout diagrams, cache directory references, and plugin directory paths.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Possibly related PRs

  • AgentWorkforce/workforce#35: Touches the same CLI session filesystem paths and updates packages/cli/src/cli.ts for the ~/.agent-workforce to ~/.agentworkforce/workforce migration.
  • AgentWorkforce/workforce#50: Updates the same home directory layout migration, changing CLI and README code that constructs or documents the new ~/.agentworkforce/workforce/ paths.

Poem

🐰 Paths refactored with careful grace,
From agent-workforce to its new place,
Through directories neat and directories deep,
These sessions and stages their promises keep!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 66.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly and specifically describes the main change—moving the session root directory to a new path.
Description check✅ PassedThe description directly addresses the changeset, detailing the path migration and documentation updates across multiple files.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-fix-mount-path

Comment @coderabbitai help to get the list of available commands and usage tips.

@devin-ai-integrationdevin-ai-integrationBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/cli.ts`:
- Around line 529-531: The generateSessionRoot function uses personaId directly
in a filesystem path which allows path-traversal; sanitize personaId before
constructing the path (in generateSessionRoot) by stripping or replacing path
separators and any ".." sequences (e.g., collapse to a safe token via
path.basename, a strict allowlist, or url-safe encoding) and reject or normalize
empty/invalid values; after building the path verify it is confined inside the
intended sessions directory (resolve and ensure it startsWith the sessions root)
and fail rather than return a path outside, and make the same validation
expectation clear for any caller like removeSessionRoot that performs recursive
rmSync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ab41c5a1-d4e5-4f43-a1b4-14b46775503e

📥 Commits

Reviewing files that changed from the base of the PR and between a1b5d92 and ff0ddfa.

📒 Files selected for processing (4)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/cli.ts
  • packages/workload-router/src/index.ts

Comment on lines 529 to +531
function generateSessionRoot(personaId: string): string {
const sessionId = `${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
return join(homedir(), '.agent-workforce', 'sessions', `${personaId}-${sessionId}`);
return join(homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== Session root construction and cleanup =="
rg -n "function generateSessionRoot|function removeSessionRoot|rmSync\\(sessionRoot" packages/cli/src/cli.ts -C2
echoecho"== Persona ID validation rules =="
rg -n "persona\\[\\$\\{expectedIntent\\}\\]\\.id|id must be a non-empty string" packages/workload-router/src/index.ts -C2
echoecho"== Check for strict persona-id regex validation =="
rg -n "persona.*id.*regex|id.*match\\(|assert.*id|INPUT_NAME_RE|SKILL_NAME_RE" packages/workload-router/src/index.ts -C2

Repository: AgentWorkforce/workforce

Length of output: 3399


🏁 Script executed:

# Find where generateSessionRoot is called
rg -n "generateSessionRoot" packages/cli/src/cli.ts -B2 -A2

Repository: AgentWorkforce/workforce

Length of output: 683


🏁 Script executed:

# Check how personaId is obtained/validated before being used
rg -n "personaId|persona.*id" packages/cli/src/cli.ts | head -50

Repository: AgentWorkforce/workforce

Length of output: 2344


🏁 Script executed:

# Check Node.js path.join behavior - understand the actual impact
node <<'EOF'const path = require('path');const os = require('os');// Test cases for path.join with potential traversal attemptsconst testCases = [ "normal-persona", "../../../tmp", "../../tmp", "/etc/passwd", "persona/../../tmp", "..%2F..%2Ftmp"];console.log("Testing path.join() behavior:");testCases.forEach(personaId => { const sessionId = "abc123"; const result = path.join( os.homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}` ); console.log(`\nPersonaId: "${personaId}"`); console.log(`Result: ${result}`);});EOF

Repository: AgentWorkforce/workforce

Length of output: 643


🏁 Script executed:

# Check CLI parsing for personaId to see if there's any validation
rg -n "spec\.id|spec = |--persona" packages/cli/src/cli.ts -B3 -A3 | head -80

Repository: AgentWorkforce/workforce

Length of output: 3104


🏁 Script executed:

# Check what spec object is and where it comes from
rg -n "const spec|interface.*Spec|type.*Spec" packages/cli/src/cli.ts | head -20

Repository: AgentWorkforce/workforce

Length of output: 360


🏁 Script executed:

# Check if there's any validation between CLI parsing and generateSessionRoot usage
rg -n "effectiveSelection|getEffectiveSelection" packages/cli/src/cli.ts -B2 -A5

Repository: AgentWorkforce/workforce

Length of output: 6379


🏁 Script executed:

# Trace the flow from CLI argument parsing to generateSessionRoot# Check runPersonaAgent function to see complete flow
rg -n "runPersonaAgent|function runPersonaAgent" packages/cli/src/cli.ts -A100 | head -150

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Check the agent command implementation
rg -n "function.*agent\(|case 'agent'" packages/cli/src/cli.ts -A20 | head -100

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Search for where personaId gets picked/selected before being used
rg -n "pick|select|findPersona|getPersona" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 1977


🏁 Script executed:

# Find the main dispatch for CLI commands to understand flow better
rg -n "if.*command|switch.*command|case" packages/cli/src/cli.ts | head -40

Repository: AgentWorkforce/workforce

Length of output: 673


🏁 Script executed:

# Look for where personaId comes from in the agent flow - search backwards from generateSessionRoot
sed -n '1095,1100p' packages/cli/src/cli.ts

Repository: AgentWorkforce/workforce

Length of output: 379


🏁 Script executed:

# Find where the selection object is created that has personaId
rg -n "PersonaSelection|selection.*=.*{" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 196


🏁 Script executed:

# Find the agent command main entry point
rg -n "subcommand === 'agent'" packages/cli/src/cli.ts -A50 | head -80

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Check what happens with the agent personaId - trace the flow
sed -n '2571,2600p' packages/cli/src/cli.ts

Repository: AgentWorkforce/workforce

Length of output: 841


🏁 Script executed:

# Check the parseAgentArg or similar function
rg -n "parseAgentArg|parsePersonaId|function.*agent" packages/cli/src/cli.ts -B2 -A10 | head -60

Repository: AgentWorkforce/workforce

Length of output: 858


🏁 Script executed:

# Find runAgentSelector function
rg -n "function runAgentSelector|runAgentSelector.*=" packages/cli/src/cli.ts -A50 | head -100

Repository: AgentWorkforce/workforce

Length of output: 1716


🏁 Script executed:

# Check how selector is parsed - likely has @ symbol for tier selection
rg -n "selector|@" packages/cli/src/cli.ts | grep -E "(selector|@)"| head -40

Repository: AgentWorkforce/workforce

Length of output: 2411


🏁 Script executed:

# Find parseSelector function
rg -n "function parseSelector" packages/cli/src/cli.ts -A30

Repository: AgentWorkforce/workforce

Length of output: 1671


🏁 Script executed:

# Check if persona IDs loaded from specs have any validation
rg -n "listBuiltInPersonas|local.byId|spec.id" packages/cli/src/cli.ts | head -20

Repository: AgentWorkforce/workforce

Length of output: 979


🏁 Script executed:

# Trace where persona specs are loaded/validated - check if there's validation
rg -n "byId|buildSelection" packages/cli/src/cli.ts -B5 -A5 | head -60

Repository: AgentWorkforce/workforce

Length of output: 2637


🏁 Script executed:

# Check where local personas are loaded/stored and if they're validated
rg -n "local\\.byId|loadLocal|personas.json|PersonaSpec" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 1321


🏁 Script executed:

# Check the PersonaSpec type definition - see if id field is validated
rg -n "type PersonaSpec|interface PersonaSpec" packages/ -B2 -A15 | head -50

Repository: AgentWorkforce/workforce

Length of output: 3549


🏁 Script executed:

# Check if there are any validations on persona IDs when they're loaded/parsed
rg -n "persona.*id|validatePersona|validateId" packages/ | grep -v node_modules | head -30

Repository: AgentWorkforce/workforce

Length of output: 18165


Sanitize personaId before building sessionRoot path.

At line 531, personaId is used directly in the filesystem path without sanitization. Because personaId comes from user input (CLI --persona flag and agentworkforce agent <id> commands), malicious values containing path separators or .. sequences escape the .../sessions/ directory. Combined with removeSessionRoot's recursive rmSync, this enables deletion of arbitrary directories outside the intended session folder.

🔧 Proposed fix
 function generateSessionRoot(personaId: string): string {
+ const safePersonaId = personaId.replace(/[^A-Za-z0-9._-]/g, '_');
const sessionId = `${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
- return join(homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}`);+ return join(+ homedir(),+ '.agentworkforce',+ 'workforce',+ 'sessions',+ `${safePersonaId}-${sessionId}`+ );
}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
functiongenerateSessionRoot(personaId: string): string{
constsessionId=`${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
returnjoin(homedir(),'.agent-workforce','sessions',`${personaId}-${sessionId}`);
returnjoin(homedir(),'.agentworkforce','workforce','sessions',`${personaId}-${sessionId}`);
functiongenerateSessionRoot(personaId: string): string{
constsafePersonaId=personaId.replace(/[^A-Za-z0-9._-]/g,'_');
constsessionId=`${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
returnjoin(
homedir(),
'.agentworkforce',
'workforce',
'sessions',
`${safePersonaId}-${sessionId}`
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/cli.ts` around lines 529 - 531, The generateSessionRoot
function uses personaId directly in a filesystem path which allows
path-traversal; sanitize personaId before constructing the path (in
generateSessionRoot) by stripping or replacing path separators and any ".."
sequences (e.g., collapse to a safe token via path.basename, a strict allowlist,
or url-safe encoding) and reject or normalize empty/invalid values; after
building the path verify it is confined inside the intended sessions directory
(resolve and ensure it startsWith the sessions root) and fail rather than return
a path outside, and make the same validation expectation clear for any caller
like removeSessionRoot that performs recursive rmSync.

@willwashburn
willwashburn merged commit 3c97a11 into mainMay 8, 2026
3 checks passed
@willwashburn
willwashburn deleted the worktree-fix-mount-path branch May 8, 2026 17:37
Sign up for freeto 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.

1 participant

@willwashburn
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Move session root to ~/.agentworkforce/workforce/sessions - #60

Merged
willwashburn merged 1 commit into
mainfrom
worktree-fix-mount-path
May 8, 2026
Merged

Move session root to ~/.agentworkforce/workforce/sessions#60
willwashburn merged 1 commit into
mainfrom
worktree-fix-mount-path

Conversation

@willwashburn

Copy link
Copy Markdown
Member

Summary

  • Update generateSessionRoot in packages/cli/src/cli.ts so the per-session staging directory lives at ~/.agentworkforce/workforce/sessions/<id>/ instead of ~/.agent-workforce/sessions/<id>/. This aligns the mount path (<root>/mount/) and skill-stage path (<root>/claude/plugin/) with the rest of the project's home-dir layout (~/.agentworkforce/workforce/personas, ~/.agentworkforce/workforce/config.json).
  • Refresh the surrounding doc comments in packages/cli/src/cli.ts and packages/workload-router/src/index.ts, plus the layout examples in README.md and packages/cli/README.md, so the documented paths match the new behavior.

Test plan

  • pnpm -r build
  • pnpm -r --filter @agentworkforce/cli --filter @agentworkforce/workload-router typecheck
  • pnpm -r --filter @agentworkforce/cli --filter @agentworkforce/workload-router test (129 cli tests, workload-router tests pass)
  • Smoke-test an interactive agentworkforce agent run and confirm ~/.agentworkforce/workforce/sessions/<id>/mount/ is created and removed on exit

🤖 Generated with Claude Code

Align the CLI's interactive-session staging directory with the rest of
the project's home-dir conventions (~/.agentworkforce/workforce/personas,
~/.agentworkforce/workforce/config.json) so the mount path lands at
~/.agentworkforce/workforce/sessions/<id>/mount/ instead of
~/.agent-workforce/sessions/<id>/mount/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR migrates the interactive session and skill staging root directory from ~/.agent-workforce/ to ~/.agentworkforce/workforce/. The session path generation logic in the CLI is updated, and all supporting documentation across both root and packages READMEs is synchronized to reflect this structural change.

Changes

Home Directory Path Migration

Layer / File(s)Summary
Path Generation Logic
packages/cli/src/cli.ts
generateSessionRoot() now constructs session directories under ~/.agentworkforce/workforce/sessions/ instead of ~/.agent-workforce/sessions/. Session cleanup comment updated to match.
Implementation Documentation
packages/workload-router/src/index.ts
SkillMaterializationOptions doc comment path example updated from ~/.agent-workforce/... to ~/.agentworkforce/....
User-Facing Documentation
README.md, packages/cli/README.md
All references to the home directory and session/stage paths updated across skill staging sections, sandbox mount documentation, session layout diagrams, cache directory references, and plugin directory paths.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Possibly related PRs

  • AgentWorkforce/workforce#35: Touches the same CLI session filesystem paths and updates packages/cli/src/cli.ts for the ~/.agent-workforce to ~/.agentworkforce/workforce migration.
  • AgentWorkforce/workforce#50: Updates the same home directory layout migration, changing CLI and README code that constructs or documents the new ~/.agentworkforce/workforce/ paths.

Poem

🐰 Paths refactored with careful grace,
From agent-workforce to its new place,
Through directories neat and directories deep,
These sessions and stages their promises keep!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 66.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly and specifically describes the main change—moving the session root directory to a new path.
Description check✅ PassedThe description directly addresses the changeset, detailing the path migration and documentation updates across multiple files.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-fix-mount-path

Comment @coderabbitai help to get the list of available commands and usage tips.

@devin-ai-integrationdevin-ai-integrationBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/cli.ts`:
- Around line 529-531: The generateSessionRoot function uses personaId directly
in a filesystem path which allows path-traversal; sanitize personaId before
constructing the path (in generateSessionRoot) by stripping or replacing path
separators and any ".." sequences (e.g., collapse to a safe token via
path.basename, a strict allowlist, or url-safe encoding) and reject or normalize
empty/invalid values; after building the path verify it is confined inside the
intended sessions directory (resolve and ensure it startsWith the sessions root)
and fail rather than return a path outside, and make the same validation
expectation clear for any caller like removeSessionRoot that performs recursive
rmSync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ab41c5a1-d4e5-4f43-a1b4-14b46775503e

📥 Commits

Reviewing files that changed from the base of the PR and between a1b5d92 and ff0ddfa.

📒 Files selected for processing (4)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/cli.ts
  • packages/workload-router/src/index.ts

Comment on lines 529 to +531
function generateSessionRoot(personaId: string): string {
const sessionId = `${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
return join(homedir(), '.agent-workforce', 'sessions', `${personaId}-${sessionId}`);
return join(homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== Session root construction and cleanup =="
rg -n "function generateSessionRoot|function removeSessionRoot|rmSync\\(sessionRoot" packages/cli/src/cli.ts -C2
echoecho"== Persona ID validation rules =="
rg -n "persona\\[\\$\\{expectedIntent\\}\\]\\.id|id must be a non-empty string" packages/workload-router/src/index.ts -C2
echoecho"== Check for strict persona-id regex validation =="
rg -n "persona.*id.*regex|id.*match\\(|assert.*id|INPUT_NAME_RE|SKILL_NAME_RE" packages/workload-router/src/index.ts -C2

Repository: AgentWorkforce/workforce

Length of output: 3399


🏁 Script executed:

# Find where generateSessionRoot is called
rg -n "generateSessionRoot" packages/cli/src/cli.ts -B2 -A2

Repository: AgentWorkforce/workforce

Length of output: 683


🏁 Script executed:

# Check how personaId is obtained/validated before being used
rg -n "personaId|persona.*id" packages/cli/src/cli.ts | head -50

Repository: AgentWorkforce/workforce

Length of output: 2344


🏁 Script executed:

# Check Node.js path.join behavior - understand the actual impact
node <<'EOF'const path = require('path');const os = require('os');// Test cases for path.join with potential traversal attemptsconst testCases = [ "normal-persona", "../../../tmp", "../../tmp", "/etc/passwd", "persona/../../tmp", "..%2F..%2Ftmp"];console.log("Testing path.join() behavior:");testCases.forEach(personaId => { const sessionId = "abc123"; const result = path.join( os.homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}` ); console.log(`\nPersonaId: "${personaId}"`); console.log(`Result: ${result}`);});EOF

Repository: AgentWorkforce/workforce

Length of output: 643


🏁 Script executed:

# Check CLI parsing for personaId to see if there's any validation
rg -n "spec\.id|spec = |--persona" packages/cli/src/cli.ts -B3 -A3 | head -80

Repository: AgentWorkforce/workforce

Length of output: 3104


🏁 Script executed:

# Check what spec object is and where it comes from
rg -n "const spec|interface.*Spec|type.*Spec" packages/cli/src/cli.ts | head -20

Repository: AgentWorkforce/workforce

Length of output: 360


🏁 Script executed:

# Check if there's any validation between CLI parsing and generateSessionRoot usage
rg -n "effectiveSelection|getEffectiveSelection" packages/cli/src/cli.ts -B2 -A5

Repository: AgentWorkforce/workforce

Length of output: 6379


🏁 Script executed:

# Trace the flow from CLI argument parsing to generateSessionRoot# Check runPersonaAgent function to see complete flow
rg -n "runPersonaAgent|function runPersonaAgent" packages/cli/src/cli.ts -A100 | head -150

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Check the agent command implementation
rg -n "function.*agent\(|case 'agent'" packages/cli/src/cli.ts -A20 | head -100

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Search for where personaId gets picked/selected before being used
rg -n "pick|select|findPersona|getPersona" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 1977


🏁 Script executed:

# Find the main dispatch for CLI commands to understand flow better
rg -n "if.*command|switch.*command|case" packages/cli/src/cli.ts | head -40

Repository: AgentWorkforce/workforce

Length of output: 673


🏁 Script executed:

# Look for where personaId comes from in the agent flow - search backwards from generateSessionRoot
sed -n '1095,1100p' packages/cli/src/cli.ts

Repository: AgentWorkforce/workforce

Length of output: 379


🏁 Script executed:

# Find where the selection object is created that has personaId
rg -n "PersonaSelection|selection.*=.*{" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 196


🏁 Script executed:

# Find the agent command main entry point
rg -n "subcommand === 'agent'" packages/cli/src/cli.ts -A50 | head -80

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Check what happens with the agent personaId - trace the flow
sed -n '2571,2600p' packages/cli/src/cli.ts

Repository: AgentWorkforce/workforce

Length of output: 841


🏁 Script executed:

# Check the parseAgentArg or similar function
rg -n "parseAgentArg|parsePersonaId|function.*agent" packages/cli/src/cli.ts -B2 -A10 | head -60

Repository: AgentWorkforce/workforce

Length of output: 858


🏁 Script executed:

# Find runAgentSelector function
rg -n "function runAgentSelector|runAgentSelector.*=" packages/cli/src/cli.ts -A50 | head -100

Repository: AgentWorkforce/workforce

Length of output: 1716


🏁 Script executed:

# Check how selector is parsed - likely has @ symbol for tier selection
rg -n "selector|@" packages/cli/src/cli.ts | grep -E "(selector|@)"| head -40

Repository: AgentWorkforce/workforce

Length of output: 2411


🏁 Script executed:

# Find parseSelector function
rg -n "function parseSelector" packages/cli/src/cli.ts -A30

Repository: AgentWorkforce/workforce

Length of output: 1671


🏁 Script executed:

# Check if persona IDs loaded from specs have any validation
rg -n "listBuiltInPersonas|local.byId|spec.id" packages/cli/src/cli.ts | head -20

Repository: AgentWorkforce/workforce

Length of output: 979


🏁 Script executed:

# Trace where persona specs are loaded/validated - check if there's validation
rg -n "byId|buildSelection" packages/cli/src/cli.ts -B5 -A5 | head -60

Repository: AgentWorkforce/workforce

Length of output: 2637


🏁 Script executed:

# Check where local personas are loaded/stored and if they're validated
rg -n "local\\.byId|loadLocal|personas.json|PersonaSpec" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 1321


🏁 Script executed:

# Check the PersonaSpec type definition - see if id field is validated
rg -n "type PersonaSpec|interface PersonaSpec" packages/ -B2 -A15 | head -50

Repository: AgentWorkforce/workforce

Length of output: 3549


🏁 Script executed:

# Check if there are any validations on persona IDs when they're loaded/parsed
rg -n "persona.*id|validatePersona|validateId" packages/ | grep -v node_modules | head -30

Repository: AgentWorkforce/workforce

Length of output: 18165


Sanitize personaId before building sessionRoot path.

At line 531, personaId is used directly in the filesystem path without sanitization. Because personaId comes from user input (CLI --persona flag and agentworkforce agent <id> commands), malicious values containing path separators or .. sequences escape the .../sessions/ directory. Combined with removeSessionRoot's recursive rmSync, this enables deletion of arbitrary directories outside the intended session folder.

🔧 Proposed fix
 function generateSessionRoot(personaId: string): string {
+ const safePersonaId = personaId.replace(/[^A-Za-z0-9._-]/g, '_');
const sessionId = `${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
- return join(homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}`);+ return join(+ homedir(),+ '.agentworkforce',+ 'workforce',+ 'sessions',+ `${safePersonaId}-${sessionId}`+ );
}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
functiongenerateSessionRoot(personaId: string): string{
constsessionId=`${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
returnjoin(homedir(),'.agent-workforce','sessions',`${personaId}-${sessionId}`);
returnjoin(homedir(),'.agentworkforce','workforce','sessions',`${personaId}-${sessionId}`);
functiongenerateSessionRoot(personaId: string): string{
constsafePersonaId=personaId.replace(/[^A-Za-z0-9._-]/g,'_');
constsessionId=`${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
returnjoin(
homedir(),
'.agentworkforce',
'workforce',
'sessions',
`${safePersonaId}-${sessionId}`
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/cli.ts` around lines 529 - 531, The generateSessionRoot
function uses personaId directly in a filesystem path which allows
path-traversal; sanitize personaId before constructing the path (in
generateSessionRoot) by stripping or replacing path separators and any ".."
sequences (e.g., collapse to a safe token via path.basename, a strict allowlist,
or url-safe encoding) and reject or normalize empty/invalid values; after
building the path verify it is confined inside the intended sessions directory
(resolve and ensure it startsWith the sessions root) and fail rather than return
a path outside, and make the same validation expectation clear for any caller
like removeSessionRoot that performs recursive rmSync.

@willwashburn
willwashburn merged commit 3c97a11 into mainMay 8, 2026
3 checks passed
@willwashburn
willwashburn deleted the worktree-fix-mount-path branch May 8, 2026 17:37
Sign up for freeto 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.

1 participant

@willwashburn
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Move session root to ~/.agentworkforce/workforce/sessions - #60

Merged
willwashburn merged 1 commit into
mainfrom
worktree-fix-mount-path
May 8, 2026
Merged

Move session root to ~/.agentworkforce/workforce/sessions#60
willwashburn merged 1 commit into
mainfrom
worktree-fix-mount-path

Conversation

@willwashburn

Copy link
Copy Markdown
Member

Summary

  • Update generateSessionRoot in packages/cli/src/cli.ts so the per-session staging directory lives at ~/.agentworkforce/workforce/sessions/<id>/ instead of ~/.agent-workforce/sessions/<id>/. This aligns the mount path (<root>/mount/) and skill-stage path (<root>/claude/plugin/) with the rest of the project's home-dir layout (~/.agentworkforce/workforce/personas, ~/.agentworkforce/workforce/config.json).
  • Refresh the surrounding doc comments in packages/cli/src/cli.ts and packages/workload-router/src/index.ts, plus the layout examples in README.md and packages/cli/README.md, so the documented paths match the new behavior.

Test plan

  • pnpm -r build
  • pnpm -r --filter @agentworkforce/cli --filter @agentworkforce/workload-router typecheck
  • pnpm -r --filter @agentworkforce/cli --filter @agentworkforce/workload-router test (129 cli tests, workload-router tests pass)
  • Smoke-test an interactive agentworkforce agent run and confirm ~/.agentworkforce/workforce/sessions/<id>/mount/ is created and removed on exit

🤖 Generated with Claude Code

Align the CLI's interactive-session staging directory with the rest of
the project's home-dir conventions (~/.agentworkforce/workforce/personas,
~/.agentworkforce/workforce/config.json) so the mount path lands at
~/.agentworkforce/workforce/sessions/<id>/mount/ instead of
~/.agent-workforce/sessions/<id>/mount/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR migrates the interactive session and skill staging root directory from ~/.agent-workforce/ to ~/.agentworkforce/workforce/. The session path generation logic in the CLI is updated, and all supporting documentation across both root and packages READMEs is synchronized to reflect this structural change.

Changes

Home Directory Path Migration

Layer / File(s)Summary
Path Generation Logic
packages/cli/src/cli.ts
generateSessionRoot() now constructs session directories under ~/.agentworkforce/workforce/sessions/ instead of ~/.agent-workforce/sessions/. Session cleanup comment updated to match.
Implementation Documentation
packages/workload-router/src/index.ts
SkillMaterializationOptions doc comment path example updated from ~/.agent-workforce/... to ~/.agentworkforce/....
User-Facing Documentation
README.md, packages/cli/README.md
All references to the home directory and session/stage paths updated across skill staging sections, sandbox mount documentation, session layout diagrams, cache directory references, and plugin directory paths.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Possibly related PRs

  • AgentWorkforce/workforce#35: Touches the same CLI session filesystem paths and updates packages/cli/src/cli.ts for the ~/.agent-workforce to ~/.agentworkforce/workforce migration.
  • AgentWorkforce/workforce#50: Updates the same home directory layout migration, changing CLI and README code that constructs or documents the new ~/.agentworkforce/workforce/ paths.

Poem

🐰 Paths refactored with careful grace,
From agent-workforce to its new place,
Through directories neat and directories deep,
These sessions and stages their promises keep!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 66.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly and specifically describes the main change—moving the session root directory to a new path.
Description check✅ PassedThe description directly addresses the changeset, detailing the path migration and documentation updates across multiple files.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-fix-mount-path

Comment @coderabbitai help to get the list of available commands and usage tips.

@devin-ai-integrationdevin-ai-integrationBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/cli.ts`:
- Around line 529-531: The generateSessionRoot function uses personaId directly
in a filesystem path which allows path-traversal; sanitize personaId before
constructing the path (in generateSessionRoot) by stripping or replacing path
separators and any ".." sequences (e.g., collapse to a safe token via
path.basename, a strict allowlist, or url-safe encoding) and reject or normalize
empty/invalid values; after building the path verify it is confined inside the
intended sessions directory (resolve and ensure it startsWith the sessions root)
and fail rather than return a path outside, and make the same validation
expectation clear for any caller like removeSessionRoot that performs recursive
rmSync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ab41c5a1-d4e5-4f43-a1b4-14b46775503e

📥 Commits

Reviewing files that changed from the base of the PR and between a1b5d92 and ff0ddfa.

📒 Files selected for processing (4)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/cli.ts
  • packages/workload-router/src/index.ts

Comment on lines 529 to +531
function generateSessionRoot(personaId: string): string {
const sessionId = `${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
return join(homedir(), '.agent-workforce', 'sessions', `${personaId}-${sessionId}`);
return join(homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== Session root construction and cleanup =="
rg -n "function generateSessionRoot|function removeSessionRoot|rmSync\\(sessionRoot" packages/cli/src/cli.ts -C2
echoecho"== Persona ID validation rules =="
rg -n "persona\\[\\$\\{expectedIntent\\}\\]\\.id|id must be a non-empty string" packages/workload-router/src/index.ts -C2
echoecho"== Check for strict persona-id regex validation =="
rg -n "persona.*id.*regex|id.*match\\(|assert.*id|INPUT_NAME_RE|SKILL_NAME_RE" packages/workload-router/src/index.ts -C2

Repository: AgentWorkforce/workforce

Length of output: 3399


🏁 Script executed:

# Find where generateSessionRoot is called
rg -n "generateSessionRoot" packages/cli/src/cli.ts -B2 -A2

Repository: AgentWorkforce/workforce

Length of output: 683


🏁 Script executed:

# Check how personaId is obtained/validated before being used
rg -n "personaId|persona.*id" packages/cli/src/cli.ts | head -50

Repository: AgentWorkforce/workforce

Length of output: 2344


🏁 Script executed:

# Check Node.js path.join behavior - understand the actual impact
node <<'EOF'const path = require('path');const os = require('os');// Test cases for path.join with potential traversal attemptsconst testCases = [ "normal-persona", "../../../tmp", "../../tmp", "/etc/passwd", "persona/../../tmp", "..%2F..%2Ftmp"];console.log("Testing path.join() behavior:");testCases.forEach(personaId => { const sessionId = "abc123"; const result = path.join( os.homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}` ); console.log(`\nPersonaId: "${personaId}"`); console.log(`Result: ${result}`);});EOF

Repository: AgentWorkforce/workforce

Length of output: 643


🏁 Script executed:

# Check CLI parsing for personaId to see if there's any validation
rg -n "spec\.id|spec = |--persona" packages/cli/src/cli.ts -B3 -A3 | head -80

Repository: AgentWorkforce/workforce

Length of output: 3104


🏁 Script executed:

# Check what spec object is and where it comes from
rg -n "const spec|interface.*Spec|type.*Spec" packages/cli/src/cli.ts | head -20

Repository: AgentWorkforce/workforce

Length of output: 360


🏁 Script executed:

# Check if there's any validation between CLI parsing and generateSessionRoot usage
rg -n "effectiveSelection|getEffectiveSelection" packages/cli/src/cli.ts -B2 -A5

Repository: AgentWorkforce/workforce

Length of output: 6379


🏁 Script executed:

# Trace the flow from CLI argument parsing to generateSessionRoot# Check runPersonaAgent function to see complete flow
rg -n "runPersonaAgent|function runPersonaAgent" packages/cli/src/cli.ts -A100 | head -150

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Check the agent command implementation
rg -n "function.*agent\(|case 'agent'" packages/cli/src/cli.ts -A20 | head -100

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Search for where personaId gets picked/selected before being used
rg -n "pick|select|findPersona|getPersona" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 1977


🏁 Script executed:

# Find the main dispatch for CLI commands to understand flow better
rg -n "if.*command|switch.*command|case" packages/cli/src/cli.ts | head -40

Repository: AgentWorkforce/workforce

Length of output: 673


🏁 Script executed:

# Look for where personaId comes from in the agent flow - search backwards from generateSessionRoot
sed -n '1095,1100p' packages/cli/src/cli.ts

Repository: AgentWorkforce/workforce

Length of output: 379


🏁 Script executed:

# Find where the selection object is created that has personaId
rg -n "PersonaSelection|selection.*=.*{" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 196


🏁 Script executed:

# Find the agent command main entry point
rg -n "subcommand === 'agent'" packages/cli/src/cli.ts -A50 | head -80

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Check what happens with the agent personaId - trace the flow
sed -n '2571,2600p' packages/cli/src/cli.ts

Repository: AgentWorkforce/workforce

Length of output: 841


🏁 Script executed:

# Check the parseAgentArg or similar function
rg -n "parseAgentArg|parsePersonaId|function.*agent" packages/cli/src/cli.ts -B2 -A10 | head -60

Repository: AgentWorkforce/workforce

Length of output: 858


🏁 Script executed:

# Find runAgentSelector function
rg -n "function runAgentSelector|runAgentSelector.*=" packages/cli/src/cli.ts -A50 | head -100

Repository: AgentWorkforce/workforce

Length of output: 1716


🏁 Script executed:

# Check how selector is parsed - likely has @ symbol for tier selection
rg -n "selector|@" packages/cli/src/cli.ts | grep -E "(selector|@)"| head -40

Repository: AgentWorkforce/workforce

Length of output: 2411


🏁 Script executed:

# Find parseSelector function
rg -n "function parseSelector" packages/cli/src/cli.ts -A30

Repository: AgentWorkforce/workforce

Length of output: 1671


🏁 Script executed:

# Check if persona IDs loaded from specs have any validation
rg -n "listBuiltInPersonas|local.byId|spec.id" packages/cli/src/cli.ts | head -20

Repository: AgentWorkforce/workforce

Length of output: 979


🏁 Script executed:

# Trace where persona specs are loaded/validated - check if there's validation
rg -n "byId|buildSelection" packages/cli/src/cli.ts -B5 -A5 | head -60

Repository: AgentWorkforce/workforce

Length of output: 2637


🏁 Script executed:

# Check where local personas are loaded/stored and if they're validated
rg -n "local\\.byId|loadLocal|personas.json|PersonaSpec" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 1321


🏁 Script executed:

# Check the PersonaSpec type definition - see if id field is validated
rg -n "type PersonaSpec|interface PersonaSpec" packages/ -B2 -A15 | head -50

Repository: AgentWorkforce/workforce

Length of output: 3549


🏁 Script executed:

# Check if there are any validations on persona IDs when they're loaded/parsed
rg -n "persona.*id|validatePersona|validateId" packages/ | grep -v node_modules | head -30

Repository: AgentWorkforce/workforce

Length of output: 18165


Sanitize personaId before building sessionRoot path.

At line 531, personaId is used directly in the filesystem path without sanitization. Because personaId comes from user input (CLI --persona flag and agentworkforce agent <id> commands), malicious values containing path separators or .. sequences escape the .../sessions/ directory. Combined with removeSessionRoot's recursive rmSync, this enables deletion of arbitrary directories outside the intended session folder.

🔧 Proposed fix
 function generateSessionRoot(personaId: string): string {
+ const safePersonaId = personaId.replace(/[^A-Za-z0-9._-]/g, '_');
const sessionId = `${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
- return join(homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}`);+ return join(+ homedir(),+ '.agentworkforce',+ 'workforce',+ 'sessions',+ `${safePersonaId}-${sessionId}`+ );
}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
functiongenerateSessionRoot(personaId: string): string{
constsessionId=`${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
returnjoin(homedir(),'.agent-workforce','sessions',`${personaId}-${sessionId}`);
returnjoin(homedir(),'.agentworkforce','workforce','sessions',`${personaId}-${sessionId}`);
functiongenerateSessionRoot(personaId: string): string{
constsafePersonaId=personaId.replace(/[^A-Za-z0-9._-]/g,'_');
constsessionId=`${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
returnjoin(
homedir(),
'.agentworkforce',
'workforce',
'sessions',
`${safePersonaId}-${sessionId}`
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/cli.ts` around lines 529 - 531, The generateSessionRoot
function uses personaId directly in a filesystem path which allows
path-traversal; sanitize personaId before constructing the path (in
generateSessionRoot) by stripping or replacing path separators and any ".."
sequences (e.g., collapse to a safe token via path.basename, a strict allowlist,
or url-safe encoding) and reject or normalize empty/invalid values; after
building the path verify it is confined inside the intended sessions directory
(resolve and ensure it startsWith the sessions root) and fail rather than return
a path outside, and make the same validation expectation clear for any caller
like removeSessionRoot that performs recursive rmSync.

@willwashburn
willwashburn merged commit 3c97a11 into mainMay 8, 2026
3 checks passed
@willwashburn
willwashburn deleted the worktree-fix-mount-path branch May 8, 2026 17:37
Sign up for freeto 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.

1 participant

@willwashburn
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Move session root to ~/.agentworkforce/workforce/sessions - #60

Merged
willwashburn merged 1 commit into
mainfrom
worktree-fix-mount-path
May 8, 2026
Merged

Move session root to ~/.agentworkforce/workforce/sessions#60
willwashburn merged 1 commit into
mainfrom
worktree-fix-mount-path

Conversation

@willwashburn

Copy link
Copy Markdown
Member

Summary

  • Update generateSessionRoot in packages/cli/src/cli.ts so the per-session staging directory lives at ~/.agentworkforce/workforce/sessions/<id>/ instead of ~/.agent-workforce/sessions/<id>/. This aligns the mount path (<root>/mount/) and skill-stage path (<root>/claude/plugin/) with the rest of the project's home-dir layout (~/.agentworkforce/workforce/personas, ~/.agentworkforce/workforce/config.json).
  • Refresh the surrounding doc comments in packages/cli/src/cli.ts and packages/workload-router/src/index.ts, plus the layout examples in README.md and packages/cli/README.md, so the documented paths match the new behavior.

Test plan

  • pnpm -r build
  • pnpm -r --filter @agentworkforce/cli --filter @agentworkforce/workload-router typecheck
  • pnpm -r --filter @agentworkforce/cli --filter @agentworkforce/workload-router test (129 cli tests, workload-router tests pass)
  • Smoke-test an interactive agentworkforce agent run and confirm ~/.agentworkforce/workforce/sessions/<id>/mount/ is created and removed on exit

🤖 Generated with Claude Code

Align the CLI's interactive-session staging directory with the rest of
the project's home-dir conventions (~/.agentworkforce/workforce/personas,
~/.agentworkforce/workforce/config.json) so the mount path lands at
~/.agentworkforce/workforce/sessions/<id>/mount/ instead of
~/.agent-workforce/sessions/<id>/mount/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR migrates the interactive session and skill staging root directory from ~/.agent-workforce/ to ~/.agentworkforce/workforce/. The session path generation logic in the CLI is updated, and all supporting documentation across both root and packages READMEs is synchronized to reflect this structural change.

Changes

Home Directory Path Migration

Layer / File(s)Summary
Path Generation Logic
packages/cli/src/cli.ts
generateSessionRoot() now constructs session directories under ~/.agentworkforce/workforce/sessions/ instead of ~/.agent-workforce/sessions/. Session cleanup comment updated to match.
Implementation Documentation
packages/workload-router/src/index.ts
SkillMaterializationOptions doc comment path example updated from ~/.agent-workforce/... to ~/.agentworkforce/....
User-Facing Documentation
README.md, packages/cli/README.md
All references to the home directory and session/stage paths updated across skill staging sections, sandbox mount documentation, session layout diagrams, cache directory references, and plugin directory paths.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Possibly related PRs

  • AgentWorkforce/workforce#35: Touches the same CLI session filesystem paths and updates packages/cli/src/cli.ts for the ~/.agent-workforce to ~/.agentworkforce/workforce migration.
  • AgentWorkforce/workforce#50: Updates the same home directory layout migration, changing CLI and README code that constructs or documents the new ~/.agentworkforce/workforce/ paths.

Poem

🐰 Paths refactored with careful grace,
From agent-workforce to its new place,
Through directories neat and directories deep,
These sessions and stages their promises keep!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 66.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly and specifically describes the main change—moving the session root directory to a new path.
Description check✅ PassedThe description directly addresses the changeset, detailing the path migration and documentation updates across multiple files.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-fix-mount-path

Comment @coderabbitai help to get the list of available commands and usage tips.

@devin-ai-integrationdevin-ai-integrationBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/cli.ts`:
- Around line 529-531: The generateSessionRoot function uses personaId directly
in a filesystem path which allows path-traversal; sanitize personaId before
constructing the path (in generateSessionRoot) by stripping or replacing path
separators and any ".." sequences (e.g., collapse to a safe token via
path.basename, a strict allowlist, or url-safe encoding) and reject or normalize
empty/invalid values; after building the path verify it is confined inside the
intended sessions directory (resolve and ensure it startsWith the sessions root)
and fail rather than return a path outside, and make the same validation
expectation clear for any caller like removeSessionRoot that performs recursive
rmSync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ab41c5a1-d4e5-4f43-a1b4-14b46775503e

📥 Commits

Reviewing files that changed from the base of the PR and between a1b5d92 and ff0ddfa.

📒 Files selected for processing (4)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/cli.ts
  • packages/workload-router/src/index.ts

Comment on lines 529 to +531
function generateSessionRoot(personaId: string): string {
const sessionId = `${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
return join(homedir(), '.agent-workforce', 'sessions', `${personaId}-${sessionId}`);
return join(homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== Session root construction and cleanup =="
rg -n "function generateSessionRoot|function removeSessionRoot|rmSync\\(sessionRoot" packages/cli/src/cli.ts -C2
echoecho"== Persona ID validation rules =="
rg -n "persona\\[\\$\\{expectedIntent\\}\\]\\.id|id must be a non-empty string" packages/workload-router/src/index.ts -C2
echoecho"== Check for strict persona-id regex validation =="
rg -n "persona.*id.*regex|id.*match\\(|assert.*id|INPUT_NAME_RE|SKILL_NAME_RE" packages/workload-router/src/index.ts -C2

Repository: AgentWorkforce/workforce

Length of output: 3399


🏁 Script executed:

# Find where generateSessionRoot is called
rg -n "generateSessionRoot" packages/cli/src/cli.ts -B2 -A2

Repository: AgentWorkforce/workforce

Length of output: 683


🏁 Script executed:

# Check how personaId is obtained/validated before being used
rg -n "personaId|persona.*id" packages/cli/src/cli.ts | head -50

Repository: AgentWorkforce/workforce

Length of output: 2344


🏁 Script executed:

# Check Node.js path.join behavior - understand the actual impact
node <<'EOF'const path = require('path');const os = require('os');// Test cases for path.join with potential traversal attemptsconst testCases = [ "normal-persona", "../../../tmp", "../../tmp", "/etc/passwd", "persona/../../tmp", "..%2F..%2Ftmp"];console.log("Testing path.join() behavior:");testCases.forEach(personaId => { const sessionId = "abc123"; const result = path.join( os.homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}` ); console.log(`\nPersonaId: "${personaId}"`); console.log(`Result: ${result}`);});EOF

Repository: AgentWorkforce/workforce

Length of output: 643


🏁 Script executed:

# Check CLI parsing for personaId to see if there's any validation
rg -n "spec\.id|spec = |--persona" packages/cli/src/cli.ts -B3 -A3 | head -80

Repository: AgentWorkforce/workforce

Length of output: 3104


🏁 Script executed:

# Check what spec object is and where it comes from
rg -n "const spec|interface.*Spec|type.*Spec" packages/cli/src/cli.ts | head -20

Repository: AgentWorkforce/workforce

Length of output: 360


🏁 Script executed:

# Check if there's any validation between CLI parsing and generateSessionRoot usage
rg -n "effectiveSelection|getEffectiveSelection" packages/cli/src/cli.ts -B2 -A5

Repository: AgentWorkforce/workforce

Length of output: 6379


🏁 Script executed:

# Trace the flow from CLI argument parsing to generateSessionRoot# Check runPersonaAgent function to see complete flow
rg -n "runPersonaAgent|function runPersonaAgent" packages/cli/src/cli.ts -A100 | head -150

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Check the agent command implementation
rg -n "function.*agent\(|case 'agent'" packages/cli/src/cli.ts -A20 | head -100

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Search for where personaId gets picked/selected before being used
rg -n "pick|select|findPersona|getPersona" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 1977


🏁 Script executed:

# Find the main dispatch for CLI commands to understand flow better
rg -n "if.*command|switch.*command|case" packages/cli/src/cli.ts | head -40

Repository: AgentWorkforce/workforce

Length of output: 673


🏁 Script executed:

# Look for where personaId comes from in the agent flow - search backwards from generateSessionRoot
sed -n '1095,1100p' packages/cli/src/cli.ts

Repository: AgentWorkforce/workforce

Length of output: 379


🏁 Script executed:

# Find where the selection object is created that has personaId
rg -n "PersonaSelection|selection.*=.*{" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 196


🏁 Script executed:

# Find the agent command main entry point
rg -n "subcommand === 'agent'" packages/cli/src/cli.ts -A50 | head -80

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Check what happens with the agent personaId - trace the flow
sed -n '2571,2600p' packages/cli/src/cli.ts

Repository: AgentWorkforce/workforce

Length of output: 841


🏁 Script executed:

# Check the parseAgentArg or similar function
rg -n "parseAgentArg|parsePersonaId|function.*agent" packages/cli/src/cli.ts -B2 -A10 | head -60

Repository: AgentWorkforce/workforce

Length of output: 858


🏁 Script executed:

# Find runAgentSelector function
rg -n "function runAgentSelector|runAgentSelector.*=" packages/cli/src/cli.ts -A50 | head -100

Repository: AgentWorkforce/workforce

Length of output: 1716


🏁 Script executed:

# Check how selector is parsed - likely has @ symbol for tier selection
rg -n "selector|@" packages/cli/src/cli.ts | grep -E "(selector|@)"| head -40

Repository: AgentWorkforce/workforce

Length of output: 2411


🏁 Script executed:

# Find parseSelector function
rg -n "function parseSelector" packages/cli/src/cli.ts -A30

Repository: AgentWorkforce/workforce

Length of output: 1671


🏁 Script executed:

# Check if persona IDs loaded from specs have any validation
rg -n "listBuiltInPersonas|local.byId|spec.id" packages/cli/src/cli.ts | head -20

Repository: AgentWorkforce/workforce

Length of output: 979


🏁 Script executed:

# Trace where persona specs are loaded/validated - check if there's validation
rg -n "byId|buildSelection" packages/cli/src/cli.ts -B5 -A5 | head -60

Repository: AgentWorkforce/workforce

Length of output: 2637


🏁 Script executed:

# Check where local personas are loaded/stored and if they're validated
rg -n "local\\.byId|loadLocal|personas.json|PersonaSpec" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 1321


🏁 Script executed:

# Check the PersonaSpec type definition - see if id field is validated
rg -n "type PersonaSpec|interface PersonaSpec" packages/ -B2 -A15 | head -50

Repository: AgentWorkforce/workforce

Length of output: 3549


🏁 Script executed:

# Check if there are any validations on persona IDs when they're loaded/parsed
rg -n "persona.*id|validatePersona|validateId" packages/ | grep -v node_modules | head -30

Repository: AgentWorkforce/workforce

Length of output: 18165


Sanitize personaId before building sessionRoot path.

At line 531, personaId is used directly in the filesystem path without sanitization. Because personaId comes from user input (CLI --persona flag and agentworkforce agent <id> commands), malicious values containing path separators or .. sequences escape the .../sessions/ directory. Combined with removeSessionRoot's recursive rmSync, this enables deletion of arbitrary directories outside the intended session folder.

🔧 Proposed fix
 function generateSessionRoot(personaId: string): string {
+ const safePersonaId = personaId.replace(/[^A-Za-z0-9._-]/g, '_');
const sessionId = `${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
- return join(homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}`);+ return join(+ homedir(),+ '.agentworkforce',+ 'workforce',+ 'sessions',+ `${safePersonaId}-${sessionId}`+ );
}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
functiongenerateSessionRoot(personaId: string): string{
constsessionId=`${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
returnjoin(homedir(),'.agent-workforce','sessions',`${personaId}-${sessionId}`);
returnjoin(homedir(),'.agentworkforce','workforce','sessions',`${personaId}-${sessionId}`);
functiongenerateSessionRoot(personaId: string): string{
constsafePersonaId=personaId.replace(/[^A-Za-z0-9._-]/g,'_');
constsessionId=`${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
returnjoin(
homedir(),
'.agentworkforce',
'workforce',
'sessions',
`${safePersonaId}-${sessionId}`
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/cli.ts` around lines 529 - 531, The generateSessionRoot
function uses personaId directly in a filesystem path which allows
path-traversal; sanitize personaId before constructing the path (in
generateSessionRoot) by stripping or replacing path separators and any ".."
sequences (e.g., collapse to a safe token via path.basename, a strict allowlist,
or url-safe encoding) and reject or normalize empty/invalid values; after
building the path verify it is confined inside the intended sessions directory
(resolve and ensure it startsWith the sessions root) and fail rather than return
a path outside, and make the same validation expectation clear for any caller
like removeSessionRoot that performs recursive rmSync.

@willwashburn
willwashburn merged commit 3c97a11 into mainMay 8, 2026
3 checks passed
@willwashburn
willwashburn deleted the worktree-fix-mount-path branch May 8, 2026 17:37
Sign up for freeto 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.

1 participant

@willwashburn
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Move session root to ~/.agentworkforce/workforce/sessions - #60

Merged
willwashburn merged 1 commit into
mainfrom
worktree-fix-mount-path
May 8, 2026
Merged

Move session root to ~/.agentworkforce/workforce/sessions#60
willwashburn merged 1 commit into
mainfrom
worktree-fix-mount-path

Conversation

@willwashburn

Copy link
Copy Markdown
Member

Summary

  • Update generateSessionRoot in packages/cli/src/cli.ts so the per-session staging directory lives at ~/.agentworkforce/workforce/sessions/<id>/ instead of ~/.agent-workforce/sessions/<id>/. This aligns the mount path (<root>/mount/) and skill-stage path (<root>/claude/plugin/) with the rest of the project's home-dir layout (~/.agentworkforce/workforce/personas, ~/.agentworkforce/workforce/config.json).
  • Refresh the surrounding doc comments in packages/cli/src/cli.ts and packages/workload-router/src/index.ts, plus the layout examples in README.md and packages/cli/README.md, so the documented paths match the new behavior.

Test plan

  • pnpm -r build
  • pnpm -r --filter @agentworkforce/cli --filter @agentworkforce/workload-router typecheck
  • pnpm -r --filter @agentworkforce/cli --filter @agentworkforce/workload-router test (129 cli tests, workload-router tests pass)
  • Smoke-test an interactive agentworkforce agent run and confirm ~/.agentworkforce/workforce/sessions/<id>/mount/ is created and removed on exit

🤖 Generated with Claude Code

Align the CLI's interactive-session staging directory with the rest of
the project's home-dir conventions (~/.agentworkforce/workforce/personas,
~/.agentworkforce/workforce/config.json) so the mount path lands at
~/.agentworkforce/workforce/sessions/<id>/mount/ instead of
~/.agent-workforce/sessions/<id>/mount/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR migrates the interactive session and skill staging root directory from ~/.agent-workforce/ to ~/.agentworkforce/workforce/. The session path generation logic in the CLI is updated, and all supporting documentation across both root and packages READMEs is synchronized to reflect this structural change.

Changes

Home Directory Path Migration

Layer / File(s)Summary
Path Generation Logic
packages/cli/src/cli.ts
generateSessionRoot() now constructs session directories under ~/.agentworkforce/workforce/sessions/ instead of ~/.agent-workforce/sessions/. Session cleanup comment updated to match.
Implementation Documentation
packages/workload-router/src/index.ts
SkillMaterializationOptions doc comment path example updated from ~/.agent-workforce/... to ~/.agentworkforce/....
User-Facing Documentation
README.md, packages/cli/README.md
All references to the home directory and session/stage paths updated across skill staging sections, sandbox mount documentation, session layout diagrams, cache directory references, and plugin directory paths.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Possibly related PRs

  • AgentWorkforce/workforce#35: Touches the same CLI session filesystem paths and updates packages/cli/src/cli.ts for the ~/.agent-workforce to ~/.agentworkforce/workforce migration.
  • AgentWorkforce/workforce#50: Updates the same home directory layout migration, changing CLI and README code that constructs or documents the new ~/.agentworkforce/workforce/ paths.

Poem

🐰 Paths refactored with careful grace,
From agent-workforce to its new place,
Through directories neat and directories deep,
These sessions and stages their promises keep!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 66.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly and specifically describes the main change—moving the session root directory to a new path.
Description check✅ PassedThe description directly addresses the changeset, detailing the path migration and documentation updates across multiple files.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-fix-mount-path

Comment @coderabbitai help to get the list of available commands and usage tips.

@devin-ai-integrationdevin-ai-integrationBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/cli.ts`:
- Around line 529-531: The generateSessionRoot function uses personaId directly
in a filesystem path which allows path-traversal; sanitize personaId before
constructing the path (in generateSessionRoot) by stripping or replacing path
separators and any ".." sequences (e.g., collapse to a safe token via
path.basename, a strict allowlist, or url-safe encoding) and reject or normalize
empty/invalid values; after building the path verify it is confined inside the
intended sessions directory (resolve and ensure it startsWith the sessions root)
and fail rather than return a path outside, and make the same validation
expectation clear for any caller like removeSessionRoot that performs recursive
rmSync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ab41c5a1-d4e5-4f43-a1b4-14b46775503e

📥 Commits

Reviewing files that changed from the base of the PR and between a1b5d92 and ff0ddfa.

📒 Files selected for processing (4)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/cli.ts
  • packages/workload-router/src/index.ts

Comment on lines 529 to +531
function generateSessionRoot(personaId: string): string {
const sessionId = `${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
return join(homedir(), '.agent-workforce', 'sessions', `${personaId}-${sessionId}`);
return join(homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== Session root construction and cleanup =="
rg -n "function generateSessionRoot|function removeSessionRoot|rmSync\\(sessionRoot" packages/cli/src/cli.ts -C2
echoecho"== Persona ID validation rules =="
rg -n "persona\\[\\$\\{expectedIntent\\}\\]\\.id|id must be a non-empty string" packages/workload-router/src/index.ts -C2
echoecho"== Check for strict persona-id regex validation =="
rg -n "persona.*id.*regex|id.*match\\(|assert.*id|INPUT_NAME_RE|SKILL_NAME_RE" packages/workload-router/src/index.ts -C2

Repository: AgentWorkforce/workforce

Length of output: 3399


🏁 Script executed:

# Find where generateSessionRoot is called
rg -n "generateSessionRoot" packages/cli/src/cli.ts -B2 -A2

Repository: AgentWorkforce/workforce

Length of output: 683


🏁 Script executed:

# Check how personaId is obtained/validated before being used
rg -n "personaId|persona.*id" packages/cli/src/cli.ts | head -50

Repository: AgentWorkforce/workforce

Length of output: 2344


🏁 Script executed:

# Check Node.js path.join behavior - understand the actual impact
node <<'EOF'const path = require('path');const os = require('os');// Test cases for path.join with potential traversal attemptsconst testCases = [ "normal-persona", "../../../tmp", "../../tmp", "/etc/passwd", "persona/../../tmp", "..%2F..%2Ftmp"];console.log("Testing path.join() behavior:");testCases.forEach(personaId => { const sessionId = "abc123"; const result = path.join( os.homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}` ); console.log(`\nPersonaId: "${personaId}"`); console.log(`Result: ${result}`);});EOF

Repository: AgentWorkforce/workforce

Length of output: 643


🏁 Script executed:

# Check CLI parsing for personaId to see if there's any validation
rg -n "spec\.id|spec = |--persona" packages/cli/src/cli.ts -B3 -A3 | head -80

Repository: AgentWorkforce/workforce

Length of output: 3104


🏁 Script executed:

# Check what spec object is and where it comes from
rg -n "const spec|interface.*Spec|type.*Spec" packages/cli/src/cli.ts | head -20

Repository: AgentWorkforce/workforce

Length of output: 360


🏁 Script executed:

# Check if there's any validation between CLI parsing and generateSessionRoot usage
rg -n "effectiveSelection|getEffectiveSelection" packages/cli/src/cli.ts -B2 -A5

Repository: AgentWorkforce/workforce

Length of output: 6379


🏁 Script executed:

# Trace the flow from CLI argument parsing to generateSessionRoot# Check runPersonaAgent function to see complete flow
rg -n "runPersonaAgent|function runPersonaAgent" packages/cli/src/cli.ts -A100 | head -150

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Check the agent command implementation
rg -n "function.*agent\(|case 'agent'" packages/cli/src/cli.ts -A20 | head -100

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Search for where personaId gets picked/selected before being used
rg -n "pick|select|findPersona|getPersona" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 1977


🏁 Script executed:

# Find the main dispatch for CLI commands to understand flow better
rg -n "if.*command|switch.*command|case" packages/cli/src/cli.ts | head -40

Repository: AgentWorkforce/workforce

Length of output: 673


🏁 Script executed:

# Look for where personaId comes from in the agent flow - search backwards from generateSessionRoot
sed -n '1095,1100p' packages/cli/src/cli.ts

Repository: AgentWorkforce/workforce

Length of output: 379


🏁 Script executed:

# Find where the selection object is created that has personaId
rg -n "PersonaSelection|selection.*=.*{" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 196


🏁 Script executed:

# Find the agent command main entry point
rg -n "subcommand === 'agent'" packages/cli/src/cli.ts -A50 | head -80

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Check what happens with the agent personaId - trace the flow
sed -n '2571,2600p' packages/cli/src/cli.ts

Repository: AgentWorkforce/workforce

Length of output: 841


🏁 Script executed:

# Check the parseAgentArg or similar function
rg -n "parseAgentArg|parsePersonaId|function.*agent" packages/cli/src/cli.ts -B2 -A10 | head -60

Repository: AgentWorkforce/workforce

Length of output: 858


🏁 Script executed:

# Find runAgentSelector function
rg -n "function runAgentSelector|runAgentSelector.*=" packages/cli/src/cli.ts -A50 | head -100

Repository: AgentWorkforce/workforce

Length of output: 1716


🏁 Script executed:

# Check how selector is parsed - likely has @ symbol for tier selection
rg -n "selector|@" packages/cli/src/cli.ts | grep -E "(selector|@)"| head -40

Repository: AgentWorkforce/workforce

Length of output: 2411


🏁 Script executed:

# Find parseSelector function
rg -n "function parseSelector" packages/cli/src/cli.ts -A30

Repository: AgentWorkforce/workforce

Length of output: 1671


🏁 Script executed:

# Check if persona IDs loaded from specs have any validation
rg -n "listBuiltInPersonas|local.byId|spec.id" packages/cli/src/cli.ts | head -20

Repository: AgentWorkforce/workforce

Length of output: 979


🏁 Script executed:

# Trace where persona specs are loaded/validated - check if there's validation
rg -n "byId|buildSelection" packages/cli/src/cli.ts -B5 -A5 | head -60

Repository: AgentWorkforce/workforce

Length of output: 2637


🏁 Script executed:

# Check where local personas are loaded/stored and if they're validated
rg -n "local\\.byId|loadLocal|personas.json|PersonaSpec" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 1321


🏁 Script executed:

# Check the PersonaSpec type definition - see if id field is validated
rg -n "type PersonaSpec|interface PersonaSpec" packages/ -B2 -A15 | head -50

Repository: AgentWorkforce/workforce

Length of output: 3549


🏁 Script executed:

# Check if there are any validations on persona IDs when they're loaded/parsed
rg -n "persona.*id|validatePersona|validateId" packages/ | grep -v node_modules | head -30

Repository: AgentWorkforce/workforce

Length of output: 18165


Sanitize personaId before building sessionRoot path.

At line 531, personaId is used directly in the filesystem path without sanitization. Because personaId comes from user input (CLI --persona flag and agentworkforce agent <id> commands), malicious values containing path separators or .. sequences escape the .../sessions/ directory. Combined with removeSessionRoot's recursive rmSync, this enables deletion of arbitrary directories outside the intended session folder.

🔧 Proposed fix
 function generateSessionRoot(personaId: string): string {
+ const safePersonaId = personaId.replace(/[^A-Za-z0-9._-]/g, '_');
const sessionId = `${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
- return join(homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}`);+ return join(+ homedir(),+ '.agentworkforce',+ 'workforce',+ 'sessions',+ `${safePersonaId}-${sessionId}`+ );
}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
functiongenerateSessionRoot(personaId: string): string{
constsessionId=`${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
returnjoin(homedir(),'.agent-workforce','sessions',`${personaId}-${sessionId}`);
returnjoin(homedir(),'.agentworkforce','workforce','sessions',`${personaId}-${sessionId}`);
functiongenerateSessionRoot(personaId: string): string{
constsafePersonaId=personaId.replace(/[^A-Za-z0-9._-]/g,'_');
constsessionId=`${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
returnjoin(
homedir(),
'.agentworkforce',
'workforce',
'sessions',
`${safePersonaId}-${sessionId}`
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/cli.ts` around lines 529 - 531, The generateSessionRoot
function uses personaId directly in a filesystem path which allows
path-traversal; sanitize personaId before constructing the path (in
generateSessionRoot) by stripping or replacing path separators and any ".."
sequences (e.g., collapse to a safe token via path.basename, a strict allowlist,
or url-safe encoding) and reject or normalize empty/invalid values; after
building the path verify it is confined inside the intended sessions directory
(resolve and ensure it startsWith the sessions root) and fail rather than return
a path outside, and make the same validation expectation clear for any caller
like removeSessionRoot that performs recursive rmSync.

@willwashburn
willwashburn merged commit 3c97a11 into mainMay 8, 2026
3 checks passed
@willwashburn
willwashburn deleted the worktree-fix-mount-path branch May 8, 2026 17:37
Sign up for freeto 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.

1 participant

@willwashburn
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Move session root to ~/.agentworkforce/workforce/sessions - #60

Merged
willwashburn merged 1 commit into
mainfrom
worktree-fix-mount-path
May 8, 2026
Merged

Move session root to ~/.agentworkforce/workforce/sessions#60
willwashburn merged 1 commit into
mainfrom
worktree-fix-mount-path

Conversation

@willwashburn

Copy link
Copy Markdown
Member

Summary

  • Update generateSessionRoot in packages/cli/src/cli.ts so the per-session staging directory lives at ~/.agentworkforce/workforce/sessions/<id>/ instead of ~/.agent-workforce/sessions/<id>/. This aligns the mount path (<root>/mount/) and skill-stage path (<root>/claude/plugin/) with the rest of the project's home-dir layout (~/.agentworkforce/workforce/personas, ~/.agentworkforce/workforce/config.json).
  • Refresh the surrounding doc comments in packages/cli/src/cli.ts and packages/workload-router/src/index.ts, plus the layout examples in README.md and packages/cli/README.md, so the documented paths match the new behavior.

Test plan

  • pnpm -r build
  • pnpm -r --filter @agentworkforce/cli --filter @agentworkforce/workload-router typecheck
  • pnpm -r --filter @agentworkforce/cli --filter @agentworkforce/workload-router test (129 cli tests, workload-router tests pass)
  • Smoke-test an interactive agentworkforce agent run and confirm ~/.agentworkforce/workforce/sessions/<id>/mount/ is created and removed on exit

🤖 Generated with Claude Code

Align the CLI's interactive-session staging directory with the rest of
the project's home-dir conventions (~/.agentworkforce/workforce/personas,
~/.agentworkforce/workforce/config.json) so the mount path lands at
~/.agentworkforce/workforce/sessions/<id>/mount/ instead of
~/.agent-workforce/sessions/<id>/mount/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR migrates the interactive session and skill staging root directory from ~/.agent-workforce/ to ~/.agentworkforce/workforce/. The session path generation logic in the CLI is updated, and all supporting documentation across both root and packages READMEs is synchronized to reflect this structural change.

Changes

Home Directory Path Migration

Layer / File(s)Summary
Path Generation Logic
packages/cli/src/cli.ts
generateSessionRoot() now constructs session directories under ~/.agentworkforce/workforce/sessions/ instead of ~/.agent-workforce/sessions/. Session cleanup comment updated to match.
Implementation Documentation
packages/workload-router/src/index.ts
SkillMaterializationOptions doc comment path example updated from ~/.agent-workforce/... to ~/.agentworkforce/....
User-Facing Documentation
README.md, packages/cli/README.md
All references to the home directory and session/stage paths updated across skill staging sections, sandbox mount documentation, session layout diagrams, cache directory references, and plugin directory paths.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Possibly related PRs

  • AgentWorkforce/workforce#35: Touches the same CLI session filesystem paths and updates packages/cli/src/cli.ts for the ~/.agent-workforce to ~/.agentworkforce/workforce migration.
  • AgentWorkforce/workforce#50: Updates the same home directory layout migration, changing CLI and README code that constructs or documents the new ~/.agentworkforce/workforce/ paths.

Poem

🐰 Paths refactored with careful grace,
From agent-workforce to its new place,
Through directories neat and directories deep,
These sessions and stages their promises keep!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 66.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly and specifically describes the main change—moving the session root directory to a new path.
Description check✅ PassedThe description directly addresses the changeset, detailing the path migration and documentation updates across multiple files.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-fix-mount-path

Comment @coderabbitai help to get the list of available commands and usage tips.

@devin-ai-integrationdevin-ai-integrationBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/cli.ts`:
- Around line 529-531: The generateSessionRoot function uses personaId directly
in a filesystem path which allows path-traversal; sanitize personaId before
constructing the path (in generateSessionRoot) by stripping or replacing path
separators and any ".." sequences (e.g., collapse to a safe token via
path.basename, a strict allowlist, or url-safe encoding) and reject or normalize
empty/invalid values; after building the path verify it is confined inside the
intended sessions directory (resolve and ensure it startsWith the sessions root)
and fail rather than return a path outside, and make the same validation
expectation clear for any caller like removeSessionRoot that performs recursive
rmSync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ab41c5a1-d4e5-4f43-a1b4-14b46775503e

📥 Commits

Reviewing files that changed from the base of the PR and between a1b5d92 and ff0ddfa.

📒 Files selected for processing (4)
  • README.md
  • packages/cli/README.md
  • packages/cli/src/cli.ts
  • packages/workload-router/src/index.ts

Comment on lines 529 to +531
function generateSessionRoot(personaId: string): string {
const sessionId = `${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
return join(homedir(), '.agent-workforce', 'sessions', `${personaId}-${sessionId}`);
return join(homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== Session root construction and cleanup =="
rg -n "function generateSessionRoot|function removeSessionRoot|rmSync\\(sessionRoot" packages/cli/src/cli.ts -C2
echoecho"== Persona ID validation rules =="
rg -n "persona\\[\\$\\{expectedIntent\\}\\]\\.id|id must be a non-empty string" packages/workload-router/src/index.ts -C2
echoecho"== Check for strict persona-id regex validation =="
rg -n "persona.*id.*regex|id.*match\\(|assert.*id|INPUT_NAME_RE|SKILL_NAME_RE" packages/workload-router/src/index.ts -C2

Repository: AgentWorkforce/workforce

Length of output: 3399


🏁 Script executed:

# Find where generateSessionRoot is called
rg -n "generateSessionRoot" packages/cli/src/cli.ts -B2 -A2

Repository: AgentWorkforce/workforce

Length of output: 683


🏁 Script executed:

# Check how personaId is obtained/validated before being used
rg -n "personaId|persona.*id" packages/cli/src/cli.ts | head -50

Repository: AgentWorkforce/workforce

Length of output: 2344


🏁 Script executed:

# Check Node.js path.join behavior - understand the actual impact
node <<'EOF'const path = require('path');const os = require('os');// Test cases for path.join with potential traversal attemptsconst testCases = [ "normal-persona", "../../../tmp", "../../tmp", "/etc/passwd", "persona/../../tmp", "..%2F..%2Ftmp"];console.log("Testing path.join() behavior:");testCases.forEach(personaId => { const sessionId = "abc123"; const result = path.join( os.homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}` ); console.log(`\nPersonaId: "${personaId}"`); console.log(`Result: ${result}`);});EOF

Repository: AgentWorkforce/workforce

Length of output: 643


🏁 Script executed:

# Check CLI parsing for personaId to see if there's any validation
rg -n "spec\.id|spec = |--persona" packages/cli/src/cli.ts -B3 -A3 | head -80

Repository: AgentWorkforce/workforce

Length of output: 3104


🏁 Script executed:

# Check what spec object is and where it comes from
rg -n "const spec|interface.*Spec|type.*Spec" packages/cli/src/cli.ts | head -20

Repository: AgentWorkforce/workforce

Length of output: 360


🏁 Script executed:

# Check if there's any validation between CLI parsing and generateSessionRoot usage
rg -n "effectiveSelection|getEffectiveSelection" packages/cli/src/cli.ts -B2 -A5

Repository: AgentWorkforce/workforce

Length of output: 6379


🏁 Script executed:

# Trace the flow from CLI argument parsing to generateSessionRoot# Check runPersonaAgent function to see complete flow
rg -n "runPersonaAgent|function runPersonaAgent" packages/cli/src/cli.ts -A100 | head -150

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Check the agent command implementation
rg -n "function.*agent\(|case 'agent'" packages/cli/src/cli.ts -A20 | head -100

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Search for where personaId gets picked/selected before being used
rg -n "pick|select|findPersona|getPersona" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 1977


🏁 Script executed:

# Find the main dispatch for CLI commands to understand flow better
rg -n "if.*command|switch.*command|case" packages/cli/src/cli.ts | head -40

Repository: AgentWorkforce/workforce

Length of output: 673


🏁 Script executed:

# Look for where personaId comes from in the agent flow - search backwards from generateSessionRoot
sed -n '1095,1100p' packages/cli/src/cli.ts

Repository: AgentWorkforce/workforce

Length of output: 379


🏁 Script executed:

# Find where the selection object is created that has personaId
rg -n "PersonaSelection|selection.*=.*{" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 196


🏁 Script executed:

# Find the agent command main entry point
rg -n "subcommand === 'agent'" packages/cli/src/cli.ts -A50 | head -80

Repository: AgentWorkforce/workforce

Length of output: 50


🏁 Script executed:

# Check what happens with the agent personaId - trace the flow
sed -n '2571,2600p' packages/cli/src/cli.ts

Repository: AgentWorkforce/workforce

Length of output: 841


🏁 Script executed:

# Check the parseAgentArg or similar function
rg -n "parseAgentArg|parsePersonaId|function.*agent" packages/cli/src/cli.ts -B2 -A10 | head -60

Repository: AgentWorkforce/workforce

Length of output: 858


🏁 Script executed:

# Find runAgentSelector function
rg -n "function runAgentSelector|runAgentSelector.*=" packages/cli/src/cli.ts -A50 | head -100

Repository: AgentWorkforce/workforce

Length of output: 1716


🏁 Script executed:

# Check how selector is parsed - likely has @ symbol for tier selection
rg -n "selector|@" packages/cli/src/cli.ts | grep -E "(selector|@)"| head -40

Repository: AgentWorkforce/workforce

Length of output: 2411


🏁 Script executed:

# Find parseSelector function
rg -n "function parseSelector" packages/cli/src/cli.ts -A30

Repository: AgentWorkforce/workforce

Length of output: 1671


🏁 Script executed:

# Check if persona IDs loaded from specs have any validation
rg -n "listBuiltInPersonas|local.byId|spec.id" packages/cli/src/cli.ts | head -20

Repository: AgentWorkforce/workforce

Length of output: 979


🏁 Script executed:

# Trace where persona specs are loaded/validated - check if there's validation
rg -n "byId|buildSelection" packages/cli/src/cli.ts -B5 -A5 | head -60

Repository: AgentWorkforce/workforce

Length of output: 2637


🏁 Script executed:

# Check where local personas are loaded/stored and if they're validated
rg -n "local\\.byId|loadLocal|personas.json|PersonaSpec" packages/cli/src/cli.ts | head -30

Repository: AgentWorkforce/workforce

Length of output: 1321


🏁 Script executed:

# Check the PersonaSpec type definition - see if id field is validated
rg -n "type PersonaSpec|interface PersonaSpec" packages/ -B2 -A15 | head -50

Repository: AgentWorkforce/workforce

Length of output: 3549


🏁 Script executed:

# Check if there are any validations on persona IDs when they're loaded/parsed
rg -n "persona.*id|validatePersona|validateId" packages/ | grep -v node_modules | head -30

Repository: AgentWorkforce/workforce

Length of output: 18165


Sanitize personaId before building sessionRoot path.

At line 531, personaId is used directly in the filesystem path without sanitization. Because personaId comes from user input (CLI --persona flag and agentworkforce agent <id> commands), malicious values containing path separators or .. sequences escape the .../sessions/ directory. Combined with removeSessionRoot's recursive rmSync, this enables deletion of arbitrary directories outside the intended session folder.

🔧 Proposed fix
 function generateSessionRoot(personaId: string): string {
+ const safePersonaId = personaId.replace(/[^A-Za-z0-9._-]/g, '_');
const sessionId = `${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
- return join(homedir(), '.agentworkforce', 'workforce', 'sessions', `${personaId}-${sessionId}`);+ return join(+ homedir(),+ '.agentworkforce',+ 'workforce',+ 'sessions',+ `${safePersonaId}-${sessionId}`+ );
}
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
functiongenerateSessionRoot(personaId: string): string{
constsessionId=`${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
returnjoin(homedir(),'.agent-workforce','sessions',`${personaId}-${sessionId}`);
returnjoin(homedir(),'.agentworkforce','workforce','sessions',`${personaId}-${sessionId}`);
functiongenerateSessionRoot(personaId: string): string{
constsafePersonaId=personaId.replace(/[^A-Za-z0-9._-]/g,'_');
constsessionId=`${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
returnjoin(
homedir(),
'.agentworkforce',
'workforce',
'sessions',
`${safePersonaId}-${sessionId}`
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/cli/src/cli.ts` around lines 529 - 531, The generateSessionRoot
function uses personaId directly in a filesystem path which allows
path-traversal; sanitize personaId before constructing the path (in
generateSessionRoot) by stripping or replacing path separators and any ".."
sequences (e.g., collapse to a safe token via path.basename, a strict allowlist,
or url-safe encoding) and reject or normalize empty/invalid values; after
building the path verify it is confined inside the intended sessions directory
(resolve and ensure it startsWith the sessions root) and fail rather than return
a path outside, and make the same validation expectation clear for any caller
like removeSessionRoot that performs recursive rmSync.

@willwashburn
willwashburn merged commit 3c97a11 into mainMay 8, 2026
3 checks passed
@willwashburn
willwashburn deleted the worktree-fix-mount-path branch May 8, 2026 17:37
Sign up for freeto 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.

1 participant

@willwashburn