Uh oh!
There was an error while loading. Please reload this page.
Move session root to ~/.agentworkforce/workforce/sessions - #60
Conversation
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>
📝 WalkthroughWalkthroughThis PR migrates the interactive session and skill staging root directory from ChangesHome Directory Path Migration
Estimated code review effort🎯 2 (Simple) | ⏱️ ~15 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (4)
README.mdpackages/cli/README.mdpackages/cli/src/cli.tspackages/workload-router/src/index.ts
| 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}`); |
There was a problem hiding this comment.
🧩 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 -C2Repository: AgentWorkforce/workforce
Length of output: 3399
🏁 Script executed:
# Find where generateSessionRoot is called
rg -n "generateSessionRoot" packages/cli/src/cli.ts -B2 -A2Repository: 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 -50Repository: 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}`);});EOFRepository: 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 -80Repository: 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 -20Repository: 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 -A5Repository: 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 -150Repository: 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 -100Repository: 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 -30Repository: 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 -40Repository: 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.tsRepository: 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 -30Repository: 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 -80Repository: 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.tsRepository: 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 -60Repository: AgentWorkforce/workforce
Length of output: 858
🏁 Script executed:
# Find runAgentSelector function
rg -n "function runAgentSelector|runAgentSelector.*=" packages/cli/src/cli.ts -A50 | head -100Repository: 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 -40Repository: AgentWorkforce/workforce
Length of output: 2411
🏁 Script executed:
# Find parseSelector function
rg -n "function parseSelector" packages/cli/src/cli.ts -A30Repository: 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 -20Repository: 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 -60Repository: 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 -30Repository: 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 -50Repository: 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 -30Repository: 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.
| 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.
Uh oh!
There was an error while loading. Please reload this page.
Summary
generateSessionRootinpackages/cli/src/cli.tsso 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).packages/cli/src/cli.tsandpackages/workload-router/src/index.ts, plus the layout examples inREADME.mdandpackages/cli/README.md, so the documented paths match the new behavior.Test plan
pnpm -r buildpnpm -r --filter @agentworkforce/cli --filter @agentworkforce/workload-router typecheckpnpm -r --filter @agentworkforce/cli --filter @agentworkforce/workload-router test(129 cli tests, workload-router tests pass)agentworkforce agentrun and confirm~/.agentworkforce/workforce/sessions/<id>/mount/is created and removed on exit🤖 Generated with Claude Code