From bd90485dbd91285e83da88082f8210c618fad6e8 Mon Sep 17 00:00:00 2001
From: Vojtech <119944107+cvrysanek@users.noreply.github.com>
Date: Tue, 19 May 2026 13:20:33 +0400
Subject: [PATCH] fix(web): setup script step 2 checks pwd, no auto-mkdir
(#344)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The /home onboarding page already has a visible manual "Step 3 — create
your workspace folder" instructing the user to `mkdir -p ~/
&& cd
~/` BEFORE pasting the install script into Claude Code. The pasted
script's step 2 then re-ran the same mkdir+cd, which silently overrode
an intentional alternate install path (e.g. user cd'd to
~/work/agnes-prod on purpose) and was redundant on the default path.
Step 2 now verifies the user is in `$HOME/` via `pwd`.
On mismatch it stops and asks the user to either re-paste from the
correct folder or reply `install here` to accept the current cwd.
Never auto-creates a folder.
Step 9 (restart Claude Code) references the install directory confirmed
in step 2 instead of a hardcoded `~/`, so users on a
custom path see accurate guidance.
---
CHANGELOG.md | 9 ++++
app/web/setup_instructions.py | 62 ++++++++++++++++------
tests/test_instance_config.py | 10 +++-
tests/test_setup_instructions.py | 88 ++++++++++++++++++++++++++++++++
4 files changed, 152 insertions(+), 17 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d79e708..468870f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,15 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
## [Unreleased]
### Changed
+- Setup script no longer auto-creates the workspace folder. Step 2 of
+ the pasted prompt now runs `pwd`, compares it to `$HOME/`
+ (the folder the /home page's visible Step 3 told the user to create
+ manually), and on mismatch warns + asks the user to either re-paste
+ from the right folder or reply `install here` to accept the current
+ cwd. Respects an intentional alternate install path instead of
+ silently switching the user back to the default. Step 9 (restart
+ Claude Code) now references the install directory confirmed in step 2
+ rather than a hardcoded `~/`.
- **BREAKING (marketplace identifier)**: synthetic plugin bundling flea
skills + agents renamed from `agnes-store-bundle` to `flea`. The
served `marketplace.json` now lists `flea` (previously
diff --git a/app/web/setup_instructions.py b/app/web/setup_instructions.py
index 6862bbf..1e50b8e 100644
--- a/app/web/setup_instructions.py
+++ b/app/web/setup_instructions.py
@@ -324,19 +324,29 @@ def _install_cli_lines(*, has_ca: bool, server_url_placeholder: str = "{server_u
def _init_lines(server_url_placeholder: str = "{server_url}") -> list[str]:
- """Steps 2-4 — workspace folder bootstrap, then `agnes init` + smoke verify.
+ """Steps 2-4 — workspace folder check, then `agnes init` + smoke verify.
- Step 2 (new) explicitly creates the workspace folder and cd's into it.
- Previously the script assumed Claude Code was already cd'd to a sensible
- location and ran `agnes init --workspace .` against whatever that
- happened to be. With the new explicit mkdir+cd the workspace path is
- deterministic — `~/{workspace_dir}` — and the visible /home step block
- + this scripted step stay in lockstep.
+ Step 2 verifies the user is already cd'd into the workspace folder
+ that the /home onboarding page's visible "Step 3 — create your
+ workspace folder" told them to create manually (`mkdir -p
+ ~/{workspace_dir} && cd ~/{workspace_dir}`). The pasted script
+ DOES NOT auto-create the folder — that would silently override an
+ intentional choice to install at a different path (e.g. the user
+ cd'd to `~/work/agnes-prod` on purpose). Instead we `pwd`, compare
+ to `$HOME/{workspace_dir}`, and on mismatch warn loudly and ask the
+ user to either re-paste from the right folder or explicitly confirm
+ "install here" in the current cwd.
`{workspace_dir}` and `{instance_brand}` are placeholders pre-substituted
by :func:`resolve_lines` from the operator-configured brand. Defaults
keep `~/Agnes` behavior for instances that don't set the brand knob.
+ `agnes init --workspace .` continues to use the current cwd, so once
+ step 2 has confirmed (or the user has explicitly accepted) the
+ install dir, step 3 lands in the right place. Step 9's restart-claude
+ cue references "this same directory" so users on a custom path see
+ accurate guidance.
+
`agnes init` is the workspace-rails delivery mechanism for everyone:
it authenticates with the PAT, fetches CLAUDE.md (RBAC-filtered),
writes AGNES_WORKSPACE.md (human-facing docs), installs Claude Code
@@ -352,14 +362,36 @@ def _init_lines(server_url_placeholder: str = "{server_url}") -> list[str]:
"""
return [
"",
- "2) Create the workspace folder in the user's home directory and cd into it:",
- " # POSIX (macOS / Linux / WSL):",
- " mkdir -p \"$HOME/{workspace_dir}\" && cd \"$HOME/{workspace_dir}\"",
- " # Windows (PowerShell) — only run if the user is on Windows:",
- " # New-Item -ItemType Directory -Force -Path \"$HOME\\{workspace_dir}\" | Out-Null",
- " # Set-Location \"$HOME\\{workspace_dir}\"",
+ "2) Verify the user is already in the workspace folder.",
+ " The /home page's visible \"Step 3 — create your workspace folder\"",
+ " already asked the user to run",
+ " mkdir -p ~/{workspace_dir} && cd ~/{workspace_dir}",
+ " in their terminal BEFORE pasting this script. Do NOT silently",
+ " re-create the folder here — the user may have intentionally cd'd",
+ " to a different location to install {instance_brand} there instead.",
"",
- " The remaining steps run inside this directory.",
+ " Run:",
+ " pwd",
+ " Expected: $HOME/{workspace_dir} (i.e. ~/{workspace_dir})",
+ "",
+ " If `pwd` matches the expected path: continue silently to step 3.",
+ "",
+ " If `pwd` does NOT match, STOP and tell the user verbatim:",
+ "",
+ " \"You are in , but {instance_brand} is normally",
+ " installed in ~/{workspace_dir} (see /home Step 3). Either run",
+ " mkdir -p ~/{workspace_dir} && cd ~/{workspace_dir}",
+ " in your terminal now and re-paste this setup script, OR reply",
+ " 'install here' to install {instance_brand} in ",
+ " instead. Reply 'abort' to stop.\"",
+ "",
+ " Wait for the user's reply.",
+ " - 'install here' → continue to step 3 in the current cwd.",
+ " The cwd you saw from `pwd` is the install",
+ " directory; remember it for step 9.",
+ " - 'abort' / anything else → stop without making any changes.",
+ " Do NOT run `mkdir`, do NOT `cd`, do NOT",
+ " continue to step 3.",
"",
"3) Bootstrap your {instance_brand} workspace in this directory:",
f" agnes init --server-url \"{server_url_placeholder}\" --token \"{{token}}\" --workspace .",
@@ -490,7 +522,7 @@ def _restart_claude_lines(step_num: str) -> list[str]:
return [
"",
f"{step_num}) Restart Claude Code so every plugin, MCP server, and SessionStart hook installed above actually loads:",
- " Tell me to type `/exit` (or close the Claude Code session entirely), then run `claude` again from this same `~/{workspace_dir}` directory.",
+ " Tell me to type `/exit` (or close the Claude Code session entirely), then run `claude` again from this same directory — the install dir confirmed in step 2 (`~/{workspace_dir}` on the default path, or whatever cwd the user explicitly accepted with 'install here').",
" The next session boots with all marketplace plugins, every connector's keychain entries / OAuth grants, and the agnes-welcome + refresh-marketplace SessionStart hooks active. This is the last action before the Confirm summary — once I'm back in Claude Code, setup is complete.",
]
diff --git a/tests/test_instance_config.py b/tests/test_instance_config.py
index 3851c47..513a3ce 100644
--- a/tests/test_instance_config.py
+++ b/tests/test_instance_config.py
@@ -135,7 +135,10 @@ class TestInstanceBrand:
workspace_dir="FoundryAI",
))
assert "Set up the Foundry AI CLI on this machine." in joined
- assert "mkdir -p \"$HOME/FoundryAI\"" in joined
+ # Step 2 is a pwd-check now (no auto-mkdir); brand + workspace_dir
+ # thread through the warning copy + expected-path string.
+ assert "$HOME/FoundryAI" in joined
+ assert "mkdir -p ~/FoundryAI && cd ~/FoundryAI" in joined
assert "Bootstrap your Foundry AI workspace" in joined
assert "Foundry AI workspace is ready" in joined
# No raw placeholders survive substitution.
@@ -150,7 +153,10 @@ class TestInstanceBrand:
from app.web.setup_instructions import resolve_lines
joined = "\n".join(resolve_lines("agnes.whl"))
assert "Set up the Agnes CLI on this machine." in joined
- assert "mkdir -p \"$HOME/Agnes\"" in joined
+ # Step 2 is a pwd-check (no auto-mkdir); default path threads
+ # through as `$HOME/Agnes` + the warning's manual-mkdir example.
+ assert "$HOME/Agnes" in joined
+ assert "mkdir -p ~/Agnes && cd ~/Agnes" in joined
assert "Bootstrap your Agnes workspace" in joined
assert "Agnes workspace is ready" in joined
mod._instance_config = None
diff --git a/tests/test_setup_instructions.py b/tests/test_setup_instructions.py
index ef25f9e..c80779d 100644
--- a/tests/test_setup_instructions.py
+++ b/tests/test_setup_instructions.py
@@ -1000,3 +1000,91 @@ def test_gws_prompt_emits_pass_fail_contract():
body = gws_prompt(gws_oauth_configured=False)
assert "✅ Google Workspace ready" in body
assert "❌ Google Workspace setup failed" in body
+
+
+# ---------------------------------------------------------------------------
+# Step 2 — workspace folder check (no auto-mkdir).
+# ---------------------------------------------------------------------------
+
+def test_step_2_checks_pwd_does_not_auto_mkdir():
+ """Step 2 must verify the user is already in the workspace folder
+ (the /home page Step 3 manual mkdir+cd) instead of silently re-running
+ `mkdir -p "$HOME/" && cd …` which would override an intentional
+ alternate install path.
+
+ Contract:
+ - Step 2 runs `pwd` and compares against `$HOME/`.
+ - Emits a warning message naming both the expected path and the
+ manual mkdir line the user already saw on /home.
+ - Does NOT emit `mkdir -p "$HOME/"` or the equivalent
+ PowerShell `New-Item -ItemType Directory -Force` line.
+ - Asks the user to reply 'install here' to proceed in a non-default
+ cwd, or 'abort' to stop. No automatic folder creation.
+ """
+ from app.web.setup_instructions import resolve_lines
+
+ joined = "\n".join(resolve_lines("agnes.whl"))
+
+ # The new check-only step header.
+ assert "2) Verify the user is already in the workspace folder." in joined
+
+ # `pwd` check + expected-path comparison must be present.
+ assert "pwd" in joined
+ assert "$HOME/Agnes" in joined # default workspace_dir
+ assert "~/Agnes" in joined
+
+ # Warning copy that references the /home Step 3 manual mkdir line.
+ assert "/home Step 3" in joined
+ assert "mkdir -p ~/Agnes && cd ~/Agnes" in joined
+
+ # Explicit "install here" / "abort" decision tree.
+ assert "'install here'" in joined
+ assert "'abort'" in joined
+
+ # The auto-mkdir lines from the previous step 2 must be GONE.
+ assert 'mkdir -p "$HOME/Agnes"' not in joined
+ assert 'mkdir -p "$HOME/{workspace_dir}"' not in joined
+ assert 'New-Item -ItemType Directory -Force -Path "$HOME\\Agnes"' not in joined
+ assert "Set-Location \"$HOME\\Agnes\"" not in joined
+
+
+def test_step_2_warning_substitutes_custom_brand():
+ """Custom brand + workspace_dir must thread through the new step 2
+ warning copy — no leftover placeholders, expected path matches the
+ operator's configured workspace dir."""
+ from app.web.setup_instructions import resolve_lines
+
+ joined = "\n".join(resolve_lines(
+ "agnes.whl",
+ instance_brand="Foundry AI",
+ workspace_dir="FoundryAI",
+ ))
+ assert "2) Verify the user is already in the workspace folder." in joined
+ assert "$HOME/FoundryAI" in joined
+ assert "~/FoundryAI" in joined
+ assert "mkdir -p ~/FoundryAI && cd ~/FoundryAI" in joined
+ # Brand + workspace_dir thread through the warning copy (the text
+ # wraps across two lines so we check the substrings separately).
+ assert "but Foundry AI is normally" in joined
+ assert "installed in ~/FoundryAI" in joined
+ # No placeholders survive into the rendered text.
+ assert "{workspace_dir}" not in joined
+ assert "{instance_brand}" not in joined
+
+
+def test_step_9_restart_references_install_dir_not_hardcoded():
+ """Step 9 must describe the restart cwd as the directory confirmed in
+ step 2 (mentioning the default path) rather than a bare hardcoded
+ `~/{workspace_dir}`. This keeps the wording accurate when the user
+ chose an alternate install path via 'install here' in step 2."""
+ from app.web.setup_instructions import resolve_lines
+
+ joined = "\n".join(resolve_lines("agnes.whl"))
+ assert "9) Restart Claude Code" in joined
+ # Wording references the step-2 confirmation.
+ assert "install dir confirmed in step 2" in joined
+ # Default path still mentioned as the expected baseline.
+ assert "~/Agnes" in joined
+ # The "install here" callout in the restart step keeps the user-flow
+ # connection visible.
+ assert "'install here'" in joined