From 0777dcc4fe710b5c6bd24fbe0aab1db054c06be2 Mon Sep 17 00:00:00 2001 From: Preston Seay Date: Fri, 10 Jul 2026 09:42:19 -0700 Subject: [PATCH 01/11] minds: explain private-repo create failures with actionable guidance Cloning a workspace source happens locally on the user's machine; a private github.com URL previously surfaced only raw git stderr on the Creating page (and could hang on a credential prompt). Clones now run with GIT_TERMINAL_PROMPT=0 (fail fast, credential helpers still work), the backend classifies auth-shaped github.com clone failures as GIT_AUTH_REQUIRED (new optional error_kind on the create-operation status API), and the Creating page reveals a static guidance block for that kind: you need access to this repo with GitHub credentials on this computer; sign in via 'gh auth login' (linked GitHub CLI quickstart); or clone it yourself and enter the local path instead. Co-Authored-By: Claude Fable 5 --- .../changelog/preston-minds-inspirations.md | 5 + .../minds/desktop_client/agent_creator.py | 97 ++++++++++++++++ .../desktop_client/agent_creator_test.py | 106 ++++++++++++++++++ .../imbue/minds/desktop_client/api_models.py | 7 ++ .../imbue/minds/desktop_client/api_v1.py | 4 + .../imbue/minds/desktop_client/api_v1_test.py | 39 +++++++ .../minds/desktop_client/static/creating.js | 13 ++- .../templates/pages/Creating.jinja | 24 ++++ .../minds/desktop_client/templates_test.py | 26 +++++ 9 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 apps/minds/changelog/preston-minds-inspirations.md diff --git a/apps/minds/changelog/preston-minds-inspirations.md b/apps/minds/changelog/preston-minds-inspirations.md new file mode 100644 index 0000000000..4591baeb8f --- /dev/null +++ b/apps/minds/changelog/preston-minds-inspirations.md @@ -0,0 +1,5 @@ +Creating a workspace from a private (or nonexistent) GitHub repository URL now shows helpful guidance instead of just a raw git error. When the clone fails with an authentication-shaped error for a github.com URL, the creating page explains that the repository looks private, recommends signing in with the GitHub CLI (`gh auth login`, with a link to the official quickstart), and offers the alternative of cloning the repository locally and entering its path in the form instead. + +The desktop client's local `git clone` of the workspace source now runs with `GIT_TERMINAL_PROMPT=0`, so cloning a repository this computer has no credentials for fails fast with a clear error instead of hanging on a credential prompt (mirroring the earlier fix in the FCT bootstrap's git calls). + +The create-operation status API (`GET /api/v1/workspaces/operations/create/`) gained an optional `error_kind` field carrying a machine-readable failure classification (currently `GIT_AUTH_REQUIRED`) alongside the human-readable `error` message. diff --git a/apps/minds/imbue/minds/desktop_client/agent_creator.py b/apps/minds/imbue/minds/desktop_client/agent_creator.py index 9a36860cc1..430a49e17e 100644 --- a/apps/minds/imbue/minds/desktop_client/agent_creator.py +++ b/apps/minds/imbue/minds/desktop_client/agent_creator.py @@ -208,6 +208,23 @@ class AgentCreationStatus(UpperCaseStrEnum): FAILED = auto() +class CreationErrorKind(UpperCaseStrEnum): + """Machine-readable classification of a creation failure. + + Carried alongside the human-readable ``error`` message so the creating + page can gate extra static guidance on the failure *type* instead of + substring-matching the message client-side. Only failure kinds that + change what the UI shows get a value here; unclassified failures carry + no kind and the UI shows just the error message. + """ + + # The workspace source is a github.com URL and git hit an authentication + # challenge cloning it: the repo is private (or does not exist -- GitHub + # deliberately answers both the same way, to avoid leaking which private + # repos exist) and none of this machine's git credentials can see it. + GIT_AUTH_REQUIRED = auto() + + class AgentCreationInfo(FrozenModel): """Snapshot of agent creation state, returned to callers for status polling. @@ -243,6 +260,13 @@ class AgentCreationInfo(FrozenModel): ) redirect_url: str | None = Field(default=None, description="URL to redirect to when creation is done") error: str | None = Field(default=None, description="Error message, set when status is FAILED") + error_kind: CreationErrorKind | None = Field( + default=None, + description=( + "Machine-readable classification of the failure, set alongside ``error`` when the " + "failure is recognized (see ``classify_creation_error``); ``None`` otherwise" + ), + ) def extract_repo_name(git_url: str) -> str: @@ -272,6 +296,62 @@ def _is_local_path(repo_source: str) -> bool: return repo_source.startswith(("/", "./", "../", "~")) +def _is_github_https_url(repo_source: str) -> bool: + """Check if a repo source is an http(s) URL on github.com. + + Gates the private-repo failure classification (and the sign-in guidance + the creating page shows for it, which recommends the GitHub CLI) to + sources where that guidance is actually correct. + """ + parts = urlsplit(repo_source) + if parts.scheme not in ("http", "https"): + return False + return parts.hostname in ("github.com", "www.github.com") + + +# Substrings of git's output that identify an authentication-shaped clone +# failure. ``clone_git_repo`` runs every git command with GIT_TERMINAL_PROMPT=0, +# so the no-usable-credentials case deterministically produces "could not read +# Username ... terminal prompts disabled" instead of hanging on a prompt. The +# other markers cover a credential helper supplying credentials that GitHub +# rejects ("Authentication failed"), credentials that cannot see the repo +# ("Repository not found" -- GitHub reports inaccessible and nonexistent repos +# identically), and access blocked by a policy such as SAML SSO (403). +_GIT_AUTH_OUTPUT_MARKERS: Final[tuple[str, ...]] = ( + "could not read Username", + "could not read Password", + "terminal prompts disabled", + "Authentication failed", + "Repository not found", + "The requested URL returned error: 403", +) + + +def classify_creation_error(repo_source: str, error: Exception) -> CreationErrorKind | None: + """Classify a creation failure into a ``CreationErrorKind``, when recognizable. + + Currently recognizes exactly one case: the clone of a + ``https://github.com/...`` workspace source failed with an + authentication-shaped git error (see ``_GIT_AUTH_OUTPUT_MARKERS``) -- + i.e. the repo is private (or does not exist) and this machine's git + credentials cannot see it. The creating page shows static sign-in + guidance for this kind. Every other failure returns ``None``. + + Matching git's error text is the only option here (git has no structured + error output), but the no-credentials shape is under our control: + ``clone_git_repo`` disables terminal prompting, which makes git emit its + stable "could not read Username" error for any auth challenge. + """ + if not isinstance(error, GitCloneError): + return None + if not _is_github_https_url(repo_source): + return None + message = str(error) + if any(marker in message for marker in _GIT_AUTH_OUTPUT_MARKERS): + return CreationErrorKind.GIT_AUTH_REQUIRED + return None + + def _redact_url_credentials(url: str) -> str: """Strip any ``user[:password]@`` userinfo from a URL's netloc for logging. @@ -393,6 +473,17 @@ def clone_git_repo( # which would otherwise leak tokens from credentialed URLs into logs. redacted_on_output = _RedactingOutputCallback(inner=on_output) if on_output is not None else None + # Never let git prompt for credentials on the controlling terminal: the + # desktop client has no terminal for the user to answer on, and when minds + # is launched from a dev shell the prompt would hang the creation thread + # forever. With prompts disabled, cloning a repo we lack credentials for + # fails fast with git's stable "could not read Username ... terminal + # prompts disabled" error, which ``classify_creation_error`` recognizes. + # Credential helpers (e.g. the macOS keychain) still work as usual -- only + # interactive terminal prompting is disabled. Mirrors the same fix in the + # FCT bootstrap's git calls. + git_env = {**os.environ, "GIT_TERMINAL_PROMPT": "0"} + # All steps run under the same child concurrency group so cancellation is # uniform; the failure is raised AFTER the `with cg` block to keep # GitCloneError from being wrapped in a ConcurrencyExceptionGroup. For the @@ -419,6 +510,7 @@ def clone_git_repo( cwd=clone_dir, is_checked_after=False, on_output=redacted_on_output, + env=git_env, ) if result.returncode != 0: stderr = result.stderr.strip() @@ -1361,6 +1453,7 @@ class AgentCreator(MutableModel): _canonical_agent_ids: dict[str, AgentId] = PrivateAttr(default_factory=dict) _redirect_urls: dict[str, str] = PrivateAttr(default_factory=dict) _errors: dict[str, str] = PrivateAttr(default_factory=dict) + _error_kinds: dict[str, CreationErrorKind] = PrivateAttr(default_factory=dict) _launch_modes: dict[str, LaunchMode] = PrivateAttr(default_factory=dict) _host_names: dict[str, str] = PrivateAttr(default_factory=dict) _log_queues: dict[str, queue.Queue[str]] = PrivateAttr(default_factory=dict) @@ -1498,6 +1591,7 @@ def get_creation_info(self, creation_id: CreationId) -> AgentCreationInfo | None host_name=self._host_names.get(cid_str, ""), redirect_url=self._redirect_urls.get(cid_str), error=self._errors.get(cid_str), + error_kind=self._error_kinds.get(cid_str), ) def get_log_queue(self, creation_id: CreationId) -> queue.Queue[str] | None: @@ -1882,9 +1976,12 @@ def _create_agent_background( except (GitCloneError, GitOperationError, MngrCommandError, ImbueCloudCliError, ValueError, OSError) as e: logger.opt(exception=e).error("Failed to create agent for creation {}", creation_id) log_queue.put("[minds] ERROR: {}".format(e)) + error_kind = classify_creation_error(repo_source, e) with self._lock: self._statuses[cid_str] = AgentCreationStatus.FAILED self._errors[cid_str] = str(e) + if error_kind is not None: + self._error_kinds[cid_str] = error_kind finally: log_queue.put(LOG_SENTINEL) diff --git a/apps/minds/imbue/minds/desktop_client/agent_creator_test.py b/apps/minds/imbue/minds/desktop_client/agent_creator_test.py index b72eb279a7..e47538a8ef 100644 --- a/apps/minds/imbue/minds/desktop_client/agent_creator_test.py +++ b/apps/minds/imbue/minds/desktop_client/agent_creator_test.py @@ -27,14 +27,18 @@ from imbue.minds.config.data_types import WorkspacePaths from imbue.minds.desktop_client.agent_creator import AgentCreationStatus from imbue.minds.desktop_client.agent_creator import AgentCreator +from imbue.minds.desktop_client.agent_creator import CreationErrorKind from imbue.minds.desktop_client.agent_creator import _CreateEventCapture +from imbue.minds.desktop_client.agent_creator import _GIT_AUTH_OUTPUT_MARKERS from imbue.minds.desktop_client.agent_creator import _build_mngr_create_command from imbue.minds.desktop_client.agent_creator import _is_git_worktree +from imbue.minds.desktop_client.agent_creator import _is_github_https_url from imbue.minds.desktop_client.agent_creator import _is_local_path from imbue.minds.desktop_client.agent_creator import _redact_url_credentials from imbue.minds.desktop_client.agent_creator import _redact_url_credentials_in_text from imbue.minds.desktop_client.agent_creator import _rsync_worktree_over_clone from imbue.minds.desktop_client.agent_creator import checkout_branch +from imbue.minds.desktop_client.agent_creator import classify_creation_error from imbue.minds.desktop_client.agent_creator import clone_git_repo from imbue.minds.desktop_client.agent_creator import extract_repo_name from imbue.minds.desktop_client.agent_creator import probe_workspace_through_plugin @@ -124,6 +128,70 @@ def test_is_local_path_recognises_relative_and_absolute_paths() -> None: assert not _is_local_path("git@github.com:user/repo.git") +def test_is_github_https_url_matches_only_github_http_urls() -> None: + assert _is_github_https_url("https://github.com/acme/private-repo.git") + assert _is_github_https_url("http://www.github.com/acme/private-repo") + assert not _is_github_https_url("https://gitlab.example.com/acme/repo.git") + assert not _is_github_https_url("git@github.com:acme/repo.git") + assert not _is_github_https_url("ssh://git@github.com/acme/repo.git") + assert not _is_github_https_url("/local/path/to/repo") + # A github.com path segment on another host must not match. + assert not _is_github_https_url("https://evil.example.com/github.com/acme/repo") + + +def test_classify_creation_error_flags_prompt_disabled_github_clone_failure() -> None: + """The no-usable-credentials shape: clone_git_repo disables terminal + prompting, so git fails with its stable "could not read Username" error.""" + error = GitCloneError( + "git clone failed:\nfatal: could not read Username for 'https://github.com': terminal prompts disabled" + ) + kind = classify_creation_error("https://github.com/acme/private-repo.git", error) + assert kind is CreationErrorKind.GIT_AUTH_REQUIRED + + +def test_classify_creation_error_flags_rejected_and_invisible_credentials() -> None: + """A credential helper supplying rejected credentials ("Authentication + failed") and credentials that cannot see the repo (GitHub's "Repository + not found") both classify as GIT_AUTH_REQUIRED.""" + url = "https://github.com/acme/private-repo.git" + rejected = GitCloneError( + "git clone failed:\nfatal: Authentication failed for 'https://github.com/acme/private-repo.git/'" + ) + invisible = GitCloneError( + "git fetch failed:\nremote: Repository not found.\n" + "fatal: repository 'https://github.com/acme/private-repo.git/' not found" + ) + assert classify_creation_error(url, rejected) is CreationErrorKind.GIT_AUTH_REQUIRED + assert classify_creation_error(url, invisible) is CreationErrorKind.GIT_AUTH_REQUIRED + + +def test_classify_creation_error_ignores_non_github_sources() -> None: + """The guidance recommends the GitHub CLI, so an auth failure against any + other host (or a local path) must not classify.""" + error = GitCloneError( + "git clone failed:\nfatal: Authentication failed for 'https://gitlab.example.com/acme/repo.git/'" + ) + assert classify_creation_error("https://gitlab.example.com/acme/repo.git", error) is None + assert classify_creation_error("/local/path/to/repo", error) is None + + +def test_classify_creation_error_ignores_non_auth_clone_failures() -> None: + """A github URL whose clone failed for a non-auth reason (e.g. DNS) stays + unclassified -- the private-repo guidance would be wrong.""" + error = GitCloneError( + "git clone failed:\nfatal: unable to access 'https://github.com/acme/repo.git/': " + "Could not resolve host: github.com" + ) + assert classify_creation_error("https://github.com/acme/repo.git", error) is None + + +def test_classify_creation_error_ignores_non_clone_errors() -> None: + """Only clone failures classify; a downstream mngr failure that happens to + echo an auth-shaped string must not trigger the clone guidance.""" + error = MngrCommandError("mngr create failed:\nfatal: could not read Username for 'https://github.com'") + assert classify_creation_error("https://github.com/acme/repo.git", error) is None + + def test_redact_url_credentials_strips_userinfo_for_schemed_urls() -> None: assert _redact_url_credentials("https://x-access-token:tok@github.com/user/repo") == "https://github.com/user/repo" assert _redact_url_credentials("https://github.com/user/repo") == "https://github.com/user/repo" @@ -805,6 +873,44 @@ def test_clone_git_repo_raises_on_missing_branch(tmp_path: Path) -> None: clone_git_repo(GitUrl("file://{}".format(origin)), dest, branch=GitBranch("nonexistent")) +class _AlwaysUnauthorizedHandler(BaseHTTPRequestHandler): + """Answers every request with 401 + a Basic challenge, like a private remote.""" + + def do_GET(self) -> None: + self.send_response(401) + self.send_header("WWW-Authenticate", 'Basic realm="test"') + self.end_headers() + + def log_message(self, format: str, *args: object) -> None: + del format, args + + +@pytest.mark.timeout(30) +def test_clone_git_repo_fails_fast_with_auth_shaped_error_when_remote_requires_auth(tmp_path: Path) -> None: + """A remote that answers with an auth challenge fails the clone quickly and + with an authentication-shaped error, instead of hanging on a credential + prompt: ``clone_git_repo`` runs git with terminal prompting disabled. + + The exact message varies with the machine's credential setup (no helper: + "could not read Username ... terminal prompts disabled"; a helper that + supplies rejected credentials: "Authentication failed"), so the assertion + accepts any of the auth markers ``classify_creation_error`` recognizes. + """ + server = HTTPServer(("127.0.0.1", 0), _AlwaysUnauthorizedHandler) + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + try: + url = GitUrl("http://127.0.0.1:{}/acme/private-repo.git".format(server.server_address[1])) + dest = tmp_path / "clone" + with pytest.raises(GitCloneError) as exc_info: + clone_git_repo(url, dest) + message = str(exc_info.value) + assert any(marker in message for marker in _GIT_AUTH_OUTPUT_MARKERS), message + finally: + server.shutdown() + thread.join(timeout=5) + + def test_clone_then_checkout_branch_accepts_full_commit_sha(tmp_path: Path) -> None: """``clone_git_repo(branch=<40-hex sha>)`` works -- the previous ``git clone --branch `` rejected SHAs outright. diff --git a/apps/minds/imbue/minds/desktop_client/api_models.py b/apps/minds/imbue/minds/desktop_client/api_models.py index 4eadd7d3ae..f70e00ea92 100644 --- a/apps/minds/imbue/minds/desktop_client/api_models.py +++ b/apps/minds/imbue/minds/desktop_client/api_models.py @@ -78,6 +78,13 @@ class CreateOperationStatusResponse(FrozenModel): agent_id: str | None = Field(default=None, description="The created workspace agent id, once known") redirect_url: str | None = Field(default=None, description="Absolute /goto// URL to navigate to when done") error: str | None = Field(default=None, description="Failure message, when the creation failed") + error_kind: str | None = Field( + default=None, + description=( + "Machine-readable failure classification (e.g. GIT_AUTH_REQUIRED), set when the " + "failure is recognized; the creating page gates extra static guidance on it" + ), + ) class DestroyOperationStatusResponse(FrozenModel): diff --git a/apps/minds/imbue/minds/desktop_client/api_v1.py b/apps/minds/imbue/minds/desktop_client/api_v1.py index 8926e5d1e5..f3db91cce5 100644 --- a/apps/minds/imbue/minds/desktop_client/api_v1.py +++ b/apps/minds/imbue/minds/desktop_client/api_v1.py @@ -883,6 +883,10 @@ def _handle_create_operation_status(operation_id: str) -> CreateOperationStatusR # redirects without reconstructing it client-side. redirect_url=info.redirect_url, error=info.error, + # Machine-readable failure classification (e.g. GIT_AUTH_REQUIRED for a + # private/nonexistent GitHub repo); the creating page reveals static + # guidance for kinds it knows about. + error_kind=str(info.error_kind) if info.error_kind is not None else None, ) diff --git a/apps/minds/imbue/minds/desktop_client/api_v1_test.py b/apps/minds/imbue/minds/desktop_client/api_v1_test.py index bb2011ec64..2a01ddf42b 100644 --- a/apps/minds/imbue/minds/desktop_client/api_v1_test.py +++ b/apps/minds/imbue/minds/desktop_client/api_v1_test.py @@ -21,6 +21,7 @@ from imbue.minds.desktop_client.agent_creator import AgentCreationInfo from imbue.minds.desktop_client.agent_creator import AgentCreationStatus from imbue.minds.desktop_client.agent_creator import AgentCreator +from imbue.minds.desktop_client.agent_creator import CreationErrorKind from imbue.minds.desktop_client.app import create_desktop_client from imbue.minds.desktop_client.auth import FileAuthStore from imbue.minds.desktop_client.backend_resolver import AgentDisplayInfo @@ -495,6 +496,44 @@ def test_create_operation_status_includes_status_text( assert body["kind"] == "create" assert body["status_text"] == status_text_for(str(AgentCreationStatus.INITIALIZING), launch_mode=LaunchMode.DOCKER) assert body["status_text"] + # An in-flight (non-failed) creation carries no failure classification. + assert body["error_kind"] is None + + +def test_create_operation_status_carries_error_kind_for_classified_failures( + tmp_path: Path, + root_concurrency_group: ConcurrencyGroup, + notification_dispatcher: NotificationDispatcher, +) -> None: + # A failed creation whose error was classified (e.g. a private GitHub repo + # the local git credentials cannot see) reports the machine-readable kind + # alongside the error message; the creating page gates its static sign-in + # guidance on it. + creation_id = CreationId() + creator = _StatusReportingAgentCreator( + paths=WorkspacePaths(data_dir=tmp_path / "minds"), + root_concurrency_group=root_concurrency_group, + notification_dispatcher=notification_dispatcher, + system_interface_health_tracker=SystemInterfaceHealthTracker(), + fixed_info=AgentCreationInfo( + creation_id=creation_id, + status=AgentCreationStatus.FAILED, + launch_mode=LaunchMode.DOCKER, + error="git clone failed:\nfatal: could not read Username for 'https://github.com'", + error_kind=CreationErrorKind.GIT_AUTH_REQUIRED, + ), + ) + client = _client_with_agent_creator( + tmp_path, root_concurrency_group, notification_dispatcher, agent_creator=creator + ) + + response = client.get(f"/api/v1/workspaces/operations/create/{creation_id}", headers=_auth_header()) + + assert response.status_code == 200 + body = json.loads(response.data) + assert body["status"] == "FAILED" + assert body["error"] + assert body["error_kind"] == "GIT_AUTH_REQUIRED" def test_create_workspace_full_surface_returns_202_and_threads_fields( diff --git a/apps/minds/imbue/minds/desktop_client/static/creating.js b/apps/minds/imbue/minds/desktop_client/static/creating.js index 0c7fff2f12..6d533f52e1 100644 --- a/apps/minds/imbue/minds/desktop_client/static/creating.js +++ b/apps/minds/imbue/minds/desktop_client/static/creating.js @@ -15,6 +15,7 @@ var creationFailed = false; var redirectUrl = null; var creationError = ''; + var creationErrorKind = ''; // ---- Loading screen: progress bar + rotating hints ---- var TIPS = [ @@ -68,6 +69,13 @@ if (failureView) failureView.classList.remove('hidden'); var msgEl = document.getElementById('error-message'); if (msgEl) msgEl.textContent = creationError || 'unknown error'; + // Reveal extra static guidance for recognized failure kinds (currently + // just the private-GitHub-repo case). The copy lives hidden in the + // template; the backend only classifies. + if (creationErrorKind === 'GIT_AUTH_REQUIRED') { + var authHelp = document.getElementById('git-auth-help'); + if (authHelp) authHelp.classList.remove('hidden'); + } // The prominent error box now carries the message, so clear the faint // footer caption to avoid showing it twice. var stage = document.getElementById('stage'); @@ -116,8 +124,8 @@ // the SSE 'done' event can be missed on a page reload (the log queue may // already be drained), so we poll the operation status. SSE is used only for // the live log stream. The create operation reports - // {status, is_done, redirect_url, error}; redirect_url is the absolute - // /goto// URL the server builds once the workspace is ready. + // {status, is_done, redirect_url, error, error_kind}; redirect_url is the + // absolute /goto// URL the server builds once the workspace is ready. var statusPoll = null; function applyStatus(data) { if (!data) return; @@ -128,6 +136,7 @@ } else if (data.status === 'FAILED') { creationFailed = true; creationError = data.error || 'unknown error'; + creationErrorKind = data.error_kind || ''; showFailure(); if (statusPoll) { clearInterval(statusPoll); statusPoll = null; } } else if (data.status_text && !creationFailed) { diff --git a/apps/minds/imbue/minds/desktop_client/templates/pages/Creating.jinja b/apps/minds/imbue/minds/desktop_client/templates/pages/Creating.jinja index 5867ad461e..4cf2a2851b 100644 --- a/apps/minds/imbue/minds/desktop_client/templates/pages/Creating.jinja +++ b/apps/minds/imbue/minds/desktop_client/templates/pages/Creating.jinja @@ -42,6 +42,30 @@ + {# Private-repo guidance: revealed by creating.js only when the backend #} + {# classifies the failure as GIT_AUTH_REQUIRED (the workspace source is #} + {# a github.com URL none of this computer's git credentials can see). #} + {# The copy is static template content gated on the error kind; the #} + {# backend only classifies, it never generates prose. #} +
Back to setup Home diff --git a/apps/minds/imbue/minds/desktop_client/templates_test.py b/apps/minds/imbue/minds/desktop_client/templates_test.py index a90874b48b..b2c72df6dc 100644 --- a/apps/minds/imbue/minds/desktop_client/templates_test.py +++ b/apps/minds/imbue/minds/desktop_client/templates_test.py @@ -7,6 +7,8 @@ from imbue.imbue_common.ids import InvalidRandomIdError from imbue.minds.desktop_client import templates as _templates_module +from imbue.minds.desktop_client.agent_creator import AgentCreationInfo +from imbue.minds.desktop_client.agent_creator import AgentCreationStatus from imbue.minds.desktop_client.templates import CATALOG from imbue.minds.desktop_client.templates import DEFAULT_EXPECTED_CREATION_DURATION_SECONDS from imbue.minds.desktop_client.templates import expected_creation_duration_seconds @@ -14,6 +16,7 @@ from imbue.minds.desktop_client.templates import render_auth_error_page from imbue.minds.desktop_client.templates import render_chrome_page from imbue.minds.desktop_client.templates import render_create_form +from imbue.minds.desktop_client.templates import render_creating_page from imbue.minds.desktop_client.templates import render_dev_styleguide_page from imbue.minds.desktop_client.templates import render_landing_page from imbue.minds.desktop_client.templates import render_login_page @@ -29,6 +32,7 @@ from imbue.minds.desktop_client.workspace_color import normalize_workspace_color from imbue.minds.desktop_client.workspace_color import pick_unused_create_color from imbue.minds.primitives import AIProvider +from imbue.minds.primitives import CreationId from imbue.minds.primitives import LaunchMode from imbue.minds.primitives import OneTimeCode from imbue.mngr.primitives import AgentId @@ -430,6 +434,28 @@ def test_render_create_form_shows_error_message_when_supplied() -> None: assert "Imbue cloud requires an account." in html +def test_render_creating_page_carries_hidden_git_auth_guidance() -> None: + """The creating page ships the private-repo guidance as static, hidden + content: creating.js reveals it only when the create-operation status + reports error_kind GIT_AUTH_REQUIRED. It must name the GitHub CLI sign-in + command, link the official docs, and offer the local-path alternative.""" + creation_id = CreationId() + info = AgentCreationInfo( + creation_id=creation_id, + status=AgentCreationStatus.INITIALIZING, + launch_mode=LaunchMode.DOCKER, + ) + html = render_creating_page(creation_id=creation_id, info=info) + assert 'id="git-auth-help"' in html + assert "gh auth login" in html + assert "https://docs.github.com/en/github-cli/github-cli/quickstart" in html + assert "path in the form instead of the URL" in html + # Hidden on first paint -- the block only shows for the classified failure. + guidance_index = html.index('id="git-auth-help"') + tag_end = html.index(">", guidance_index) + assert "hidden" in html[guidance_index:tag_end] + + def test_render_create_form_honors_workspace_env_vars_when_opted_in(monkeypatch: pytest.MonkeyPatch) -> None: """With the explicit opt-in, the MINDS_WORKSPACE_* env vars pre-fill the form. From 20d38970eed7f3500c01ffb4376fd812333bd342 Mon Sep 17 00:00:00 2001 From: Preston Seay Date: Fri, 10 Jul 2026 10:21:38 -0700 Subject: [PATCH 02/11] minds: extract _git_noninteractive_env helper for the clone env Same name and one-line body as the per-file copies in the FCT's bootstrap.manager and runtime_backup.runner, so the intent (never let git prompt on a terminal) is carried by the function name instead of an inline dict at the call site. Co-Authored-By: Claude Fable 5 --- .../minds/desktop_client/agent_creator.py | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/apps/minds/imbue/minds/desktop_client/agent_creator.py b/apps/minds/imbue/minds/desktop_client/agent_creator.py index 430a49e17e..6fbd5717f7 100644 --- a/apps/minds/imbue/minds/desktop_client/agent_creator.py +++ b/apps/minds/imbue/minds/desktop_client/agent_creator.py @@ -415,6 +415,26 @@ def _is_git_worktree(repo_dir: Path) -> bool: return dot_git.is_file() +def _git_noninteractive_env() -> dict[str, str]: + """Environment for the desktop client's git calls: never prompt for credentials. + + Git prompts for a username/password on the controlling terminal when a + remote needs auth and no credential is available -- but the desktop client + has no terminal for the user to answer on, and when minds is launched from + a dev shell the prompt would hang the creation thread forever. With + ``GIT_TERMINAL_PROMPT=0``, cloning a repo this machine lacks credentials + for fails fast with git's stable "could not read Username ... terminal + prompts disabled" error, which ``classify_creation_error`` recognizes. + Credential helpers (e.g. the macOS keychain) still work as usual -- only + interactive terminal prompting is disabled. + + Deliberately a small per-file copy of the same one-line helper the FCT's + ``bootstrap.manager`` and ``runtime_backup.runner`` carry (same name, same + body), rather than a shared cross-package import. + """ + return {**os.environ, "GIT_TERMINAL_PROMPT": "0"} + + def clone_git_repo( git_url: GitUrl, clone_dir: Path, @@ -473,16 +493,7 @@ def clone_git_repo( # which would otherwise leak tokens from credentialed URLs into logs. redacted_on_output = _RedactingOutputCallback(inner=on_output) if on_output is not None else None - # Never let git prompt for credentials on the controlling terminal: the - # desktop client has no terminal for the user to answer on, and when minds - # is launched from a dev shell the prompt would hang the creation thread - # forever. With prompts disabled, cloning a repo we lack credentials for - # fails fast with git's stable "could not read Username ... terminal - # prompts disabled" error, which ``classify_creation_error`` recognizes. - # Credential helpers (e.g. the macOS keychain) still work as usual -- only - # interactive terminal prompting is disabled. Mirrors the same fix in the - # FCT bootstrap's git calls. - git_env = {**os.environ, "GIT_TERMINAL_PROMPT": "0"} + git_env = _git_noninteractive_env() # All steps run under the same child concurrency group so cancellation is # uniform; the failure is raised AFTER the `with cg` block to keep From f9c576c9afbd0f24ce1a250510bcd3cfdfd29f31 Mon Sep 17 00:00:00 2001 From: Preston Seay Date: Mon, 13 Jul 2026 09:03:23 -0700 Subject: [PATCH 03/11] rename: FCT reference in agent_creator docstring to default-workspace-template Applied scripts/rename_template_repo.py --apply on this branch after merging main's rename; --check reports zero live references. Co-Authored-By: Claude Fable 5 --- apps/minds/imbue/minds/desktop_client/agent_creator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/minds/imbue/minds/desktop_client/agent_creator.py b/apps/minds/imbue/minds/desktop_client/agent_creator.py index 2de6788ce4..0ef8f0ee7c 100644 --- a/apps/minds/imbue/minds/desktop_client/agent_creator.py +++ b/apps/minds/imbue/minds/desktop_client/agent_creator.py @@ -428,7 +428,7 @@ def _git_noninteractive_env() -> dict[str, str]: Credential helpers (e.g. the macOS keychain) still work as usual -- only interactive terminal prompting is disabled. - Deliberately a small per-file copy of the same one-line helper the FCT's + Deliberately a small per-file copy of the same one-line helper the DEFAULT_WORKSPACE_TEMPLATE's ``bootstrap.manager`` and ``runtime_backup.runner`` carry (same name, same body), rather than a shared cross-package import. """ From 28194ee1ac7e9fe9e5bb2f60e0122cfd8cd65399 Mon Sep 17 00:00:00 2001 From: Preston Seay Date: Mon, 13 Jul 2026 09:04:11 -0700 Subject: [PATCH 04/11] fix docstring prose from automated template rename Co-Authored-By: Claude Fable 5 --- apps/minds/imbue/minds/desktop_client/agent_creator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/minds/imbue/minds/desktop_client/agent_creator.py b/apps/minds/imbue/minds/desktop_client/agent_creator.py index 0ef8f0ee7c..ed15cc977b 100644 --- a/apps/minds/imbue/minds/desktop_client/agent_creator.py +++ b/apps/minds/imbue/minds/desktop_client/agent_creator.py @@ -428,9 +428,9 @@ def _git_noninteractive_env() -> dict[str, str]: Credential helpers (e.g. the macOS keychain) still work as usual -- only interactive terminal prompting is disabled. - Deliberately a small per-file copy of the same one-line helper the DEFAULT_WORKSPACE_TEMPLATE's - ``bootstrap.manager`` and ``runtime_backup.runner`` carry (same name, same - body), rather than a shared cross-package import. + Deliberately a small per-file copy of the same one-line helper the default + workspace template's ``bootstrap.manager`` and ``runtime_backup.runner`` + carry (same name, same body), rather than a shared cross-package import. """ return {**os.environ, "GIT_TERMINAL_PROMPT": "0"} From b1295a3a25596bc9ff2218a0d2add95a402636b9 Mon Sep 17 00:00:00 2001 From: Preston Seay Date: Mon, 13 Jul 2026 09:18:03 -0700 Subject: [PATCH 05/11] minds: classify ANY failed github.com clone as GIT_AUTH_REQUIRED Drops the git-output substring markers: substring matching is brittle across git versions and locales, and a failed github.com clone is overwhelmingly an access problem the guidance covers -- the raw git error stays visible right above the guidance for anything rarer. The classifier is now just: GitCloneError + https github.com URL. Tests consolidated accordingly; GIT_TERMINAL_PROMPT=0 fail-fast behavior unchanged. Co-Authored-By: Claude Fable 5 --- .../changelog/preston-minds-inspirations.md | 2 +- .../minds/desktop_client/agent_creator.py | 56 ++++++------------- .../desktop_client/agent_creator_test.py | 51 ++++++----------- 3 files changed, 34 insertions(+), 75 deletions(-) diff --git a/apps/minds/changelog/preston-minds-inspirations.md b/apps/minds/changelog/preston-minds-inspirations.md index 4591baeb8f..1ba64888e3 100644 --- a/apps/minds/changelog/preston-minds-inspirations.md +++ b/apps/minds/changelog/preston-minds-inspirations.md @@ -1,4 +1,4 @@ -Creating a workspace from a private (or nonexistent) GitHub repository URL now shows helpful guidance instead of just a raw git error. When the clone fails with an authentication-shaped error for a github.com URL, the creating page explains that the repository looks private, recommends signing in with the GitHub CLI (`gh auth login`, with a link to the official quickstart), and offers the alternative of cloning the repository locally and entering its path in the form instead. +Creating a workspace from a private (or nonexistent) GitHub repository URL now shows helpful guidance instead of just a raw git error. When any clone of a github.com URL fails (deliberately no matching of git's error text -- a failed GitHub clone is overwhelmingly an access problem, and the raw error stays visible alongside), the creating page explains that the repository looks private, recommends signing in with the GitHub CLI (`gh auth login`, with a link to the official quickstart), and offers the alternative of cloning the repository locally and entering its path in the form instead. The desktop client's local `git clone` of the workspace source now runs with `GIT_TERMINAL_PROMPT=0`, so cloning a repository this computer has no credentials for fails fast with a clear error instead of hanging on a credential prompt (mirroring the earlier fix in the FCT bootstrap's git calls). diff --git a/apps/minds/imbue/minds/desktop_client/agent_creator.py b/apps/minds/imbue/minds/desktop_client/agent_creator.py index ed15cc977b..c4aa20186c 100644 --- a/apps/minds/imbue/minds/desktop_client/agent_creator.py +++ b/apps/minds/imbue/minds/desktop_client/agent_creator.py @@ -218,10 +218,11 @@ class CreationErrorKind(UpperCaseStrEnum): no kind and the UI shows just the error message. """ - # The workspace source is a github.com URL and git hit an authentication - # challenge cloning it: the repo is private (or does not exist -- GitHub + # The clone of a github.com workspace source failed. By far the most + # common cause: the repo is private (or does not exist -- GitHub # deliberately answers both the same way, to avoid leaking which private - # repos exist) and none of this machine's git credentials can see it. + # repos exist) and none of this machine's git credentials can see it, so + # the creating page shows sign-in guidance alongside the raw error. GIT_AUTH_REQUIRED = auto() @@ -309,47 +310,24 @@ def _is_github_https_url(repo_source: str) -> bool: return parts.hostname in ("github.com", "www.github.com") -# Substrings of git's output that identify an authentication-shaped clone -# failure. ``clone_git_repo`` runs every git command with GIT_TERMINAL_PROMPT=0, -# so the no-usable-credentials case deterministically produces "could not read -# Username ... terminal prompts disabled" instead of hanging on a prompt. The -# other markers cover a credential helper supplying credentials that GitHub -# rejects ("Authentication failed"), credentials that cannot see the repo -# ("Repository not found" -- GitHub reports inaccessible and nonexistent repos -# identically), and access blocked by a policy such as SAML SSO (403). -_GIT_AUTH_OUTPUT_MARKERS: Final[tuple[str, ...]] = ( - "could not read Username", - "could not read Password", - "terminal prompts disabled", - "Authentication failed", - "Repository not found", - "The requested URL returned error: 403", -) - - def classify_creation_error(repo_source: str, error: Exception) -> CreationErrorKind | None: """Classify a creation failure into a ``CreationErrorKind``, when recognizable. - Currently recognizes exactly one case: the clone of a - ``https://github.com/...`` workspace source failed with an - authentication-shaped git error (see ``_GIT_AUTH_OUTPUT_MARKERS``) -- - i.e. the repo is private (or does not exist) and this machine's git - credentials cannot see it. The creating page shows static sign-in - guidance for this kind. Every other failure returns ``None``. - - Matching git's error text is the only option here (git has no structured - error output), but the no-credentials shape is under our control: - ``clone_git_repo`` disables terminal prompting, which makes git emit its - stable "could not read Username" error for any auth challenge. + Currently recognizes exactly one case: ANY failed clone of a + ``https://github.com/...`` workspace source. Deliberately no matching of + git's error text (git has no structured error output, and substring + matching is brittle across git versions and locales): a github.com clone + that failed at all is overwhelmingly an access problem -- the repo is + private (or does not exist) and this machine's git credentials cannot see + it -- and the creating page's guidance covers that case while the raw git + error stays visible right above it for anything rarer. Every other + failure returns ``None``. """ if not isinstance(error, GitCloneError): return None if not _is_github_https_url(repo_source): return None - message = str(error) - if any(marker in message for marker in _GIT_AUTH_OUTPUT_MARKERS): - return CreationErrorKind.GIT_AUTH_REQUIRED - return None + return CreationErrorKind.GIT_AUTH_REQUIRED def _redact_url_credentials(url: str) -> str: @@ -424,9 +402,9 @@ def _git_noninteractive_env() -> dict[str, str]: a dev shell the prompt would hang the creation thread forever. With ``GIT_TERMINAL_PROMPT=0``, cloning a repo this machine lacks credentials for fails fast with git's stable "could not read Username ... terminal - prompts disabled" error, which ``classify_creation_error`` recognizes. - Credential helpers (e.g. the macOS keychain) still work as usual -- only - interactive terminal prompting is disabled. + prompts disabled" error instead of hanging. Credential helpers (e.g. the + macOS keychain) still work as usual -- only interactive terminal + prompting is disabled. Deliberately a small per-file copy of the same one-line helper the default workspace template's ``bootstrap.manager`` and ``runtime_backup.runner`` diff --git a/apps/minds/imbue/minds/desktop_client/agent_creator_test.py b/apps/minds/imbue/minds/desktop_client/agent_creator_test.py index 2c2b132d43..6df6bae529 100644 --- a/apps/minds/imbue/minds/desktop_client/agent_creator_test.py +++ b/apps/minds/imbue/minds/desktop_client/agent_creator_test.py @@ -29,7 +29,6 @@ from imbue.minds.desktop_client.agent_creator import AgentCreator from imbue.minds.desktop_client.agent_creator import CreationErrorKind from imbue.minds.desktop_client.agent_creator import _CreateEventCapture -from imbue.minds.desktop_client.agent_creator import _GIT_AUTH_OUTPUT_MARKERS from imbue.minds.desktop_client.agent_creator import _build_mngr_create_command from imbue.minds.desktop_client.agent_creator import _is_git_worktree from imbue.minds.desktop_client.agent_creator import _is_github_https_url @@ -139,34 +138,26 @@ def test_is_github_https_url_matches_only_github_http_urls() -> None: assert not _is_github_https_url("https://evil.example.com/github.com/acme/repo") -def test_classify_creation_error_flags_prompt_disabled_github_clone_failure() -> None: - """The no-usable-credentials shape: clone_git_repo disables terminal - prompting, so git fails with its stable "could not read Username" error.""" - error = GitCloneError( - "git clone failed:\nfatal: could not read Username for 'https://github.com': terminal prompts disabled" - ) - kind = classify_creation_error("https://github.com/acme/private-repo.git", error) - assert kind is CreationErrorKind.GIT_AUTH_REQUIRED - - -def test_classify_creation_error_flags_rejected_and_invisible_credentials() -> None: - """A credential helper supplying rejected credentials ("Authentication - failed") and credentials that cannot see the repo (GitHub's "Repository - not found") both classify as GIT_AUTH_REQUIRED.""" +def test_classify_creation_error_flags_any_failed_github_clone() -> None: + """ANY failed clone of a github.com source classifies -- no matching of + git's error text (deliberately: substring matching is brittle, and a + failed github clone is overwhelmingly an access problem the guidance + covers, while the raw error stays visible alongside it).""" url = "https://github.com/acme/private-repo.git" - rejected = GitCloneError( - "git clone failed:\nfatal: Authentication failed for 'https://github.com/acme/private-repo.git/'" - ) - invisible = GitCloneError( + failures = ( + "git clone failed:\nfatal: could not read Username for 'https://github.com': terminal prompts disabled", + "git clone failed:\nfatal: Authentication failed for 'https://github.com/acme/private-repo.git/'", "git fetch failed:\nremote: Repository not found.\n" - "fatal: repository 'https://github.com/acme/private-repo.git/' not found" + "fatal: repository 'https://github.com/acme/private-repo.git/' not found", + "git clone failed:\nfatal: unable to access 'https://github.com/acme/repo.git/': " + "Could not resolve host: github.com", ) - assert classify_creation_error(url, rejected) is CreationErrorKind.GIT_AUTH_REQUIRED - assert classify_creation_error(url, invisible) is CreationErrorKind.GIT_AUTH_REQUIRED + for message in failures: + assert classify_creation_error(url, GitCloneError(message)) is CreationErrorKind.GIT_AUTH_REQUIRED, message def test_classify_creation_error_ignores_non_github_sources() -> None: - """The guidance recommends the GitHub CLI, so an auth failure against any + """The guidance recommends the GitHub CLI, so a clone failure against any other host (or a local path) must not classify.""" error = GitCloneError( "git clone failed:\nfatal: Authentication failed for 'https://gitlab.example.com/acme/repo.git/'" @@ -175,16 +166,6 @@ def test_classify_creation_error_ignores_non_github_sources() -> None: assert classify_creation_error("/local/path/to/repo", error) is None -def test_classify_creation_error_ignores_non_auth_clone_failures() -> None: - """A github URL whose clone failed for a non-auth reason (e.g. DNS) stays - unclassified -- the private-repo guidance would be wrong.""" - error = GitCloneError( - "git clone failed:\nfatal: unable to access 'https://github.com/acme/repo.git/': " - "Could not resolve host: github.com" - ) - assert classify_creation_error("https://github.com/acme/repo.git", error) is None - - def test_classify_creation_error_ignores_non_clone_errors() -> None: """Only clone failures classify; a downstream mngr failure that happens to echo an auth-shaped string must not trigger the clone guidance.""" @@ -894,7 +875,7 @@ def test_clone_git_repo_fails_fast_with_auth_shaped_error_when_remote_requires_a The exact message varies with the machine's credential setup (no helper: "could not read Username ... terminal prompts disabled"; a helper that supplies rejected credentials: "Authentication failed"), so the assertion - accepts any of the auth markers ``classify_creation_error`` recognizes. + accepts either shape. """ server = HTTPServer(("127.0.0.1", 0), _AlwaysUnauthorizedHandler) thread = threading.Thread(target=server.serve_forever, daemon=True) @@ -905,7 +886,7 @@ def test_clone_git_repo_fails_fast_with_auth_shaped_error_when_remote_requires_a with pytest.raises(GitCloneError) as exc_info: clone_git_repo(url, dest) message = str(exc_info.value) - assert any(marker in message for marker in _GIT_AUTH_OUTPUT_MARKERS), message + assert "terminal prompts disabled" in message or "Authentication failed" in message, message finally: server.shutdown() thread.join(timeout=5) From cf1facf1e9ca130c07c6418b2c8566cec334d5e9 Mon Sep 17 00:00:00 2001 From: Preston Seay Date: Mon, 13 Jul 2026 17:04:55 -0700 Subject: [PATCH 06/11] minds: rename GIT_AUTH_REQUIRED -> GITHUB_AUTH_REQUIRED (don't conflate git with GitHub) The private-repo create-failure classification is specifically a GitHub access problem: it fires only for github.com URLs and the guidance recommends the GitHub CLI. Renaming the error kind (and the DOM id git-auth-help -> github-auth-help) so it names GitHub, not generic git. The genuinely-git plumbing (GitCloneError, _git_noninteractive_env, GIT_TERMINAL_PROMPT) is unchanged -- the clone mechanism is git; the surfaced problem is GitHub auth. 374 passed, ty clean. Co-Authored-By: Claude Fable 5 --- apps/minds/changelog/preston-minds-inspirations.md | 2 +- apps/minds/imbue/minds/desktop_client/agent_creator.py | 7 ++++--- .../imbue/minds/desktop_client/agent_creator_test.py | 2 +- apps/minds/imbue/minds/desktop_client/api_models.py | 2 +- apps/minds/imbue/minds/desktop_client/api_v1.py | 2 +- apps/minds/imbue/minds/desktop_client/api_v1_test.py | 4 ++-- apps/minds/imbue/minds/desktop_client/static/creating.js | 4 ++-- .../minds/desktop_client/templates/pages/Creating.jinja | 6 +++--- apps/minds/imbue/minds/desktop_client/templates_test.py | 8 ++++---- 9 files changed, 19 insertions(+), 18 deletions(-) diff --git a/apps/minds/changelog/preston-minds-inspirations.md b/apps/minds/changelog/preston-minds-inspirations.md index 1ba64888e3..b2788a272d 100644 --- a/apps/minds/changelog/preston-minds-inspirations.md +++ b/apps/minds/changelog/preston-minds-inspirations.md @@ -2,4 +2,4 @@ Creating a workspace from a private (or nonexistent) GitHub repository URL now s The desktop client's local `git clone` of the workspace source now runs with `GIT_TERMINAL_PROMPT=0`, so cloning a repository this computer has no credentials for fails fast with a clear error instead of hanging on a credential prompt (mirroring the earlier fix in the FCT bootstrap's git calls). -The create-operation status API (`GET /api/v1/workspaces/operations/create/`) gained an optional `error_kind` field carrying a machine-readable failure classification (currently `GIT_AUTH_REQUIRED`) alongside the human-readable `error` message. +The create-operation status API (`GET /api/v1/workspaces/operations/create/`) gained an optional `error_kind` field carrying a machine-readable failure classification (currently `GITHUB_AUTH_REQUIRED`) alongside the human-readable `error` message. diff --git a/apps/minds/imbue/minds/desktop_client/agent_creator.py b/apps/minds/imbue/minds/desktop_client/agent_creator.py index cee852e729..8d5b59dc2f 100644 --- a/apps/minds/imbue/minds/desktop_client/agent_creator.py +++ b/apps/minds/imbue/minds/desktop_client/agent_creator.py @@ -234,8 +234,9 @@ class CreationErrorKind(UpperCaseStrEnum): # common cause: the repo is private (or does not exist -- GitHub # deliberately answers both the same way, to avoid leaking which private # repos exist) and none of this machine's git credentials can see it, so - # the creating page shows sign-in guidance alongside the raw error. - GIT_AUTH_REQUIRED = auto() + # the creating page shows GitHub sign-in guidance alongside the raw error. + # The clone mechanism is git, but the problem we surface is GitHub access. + GITHUB_AUTH_REQUIRED = auto() class AgentCreationInfo(FrozenModel): @@ -339,7 +340,7 @@ def classify_creation_error(repo_source: str, error: Exception) -> CreationError return None if not _is_github_https_url(repo_source): return None - return CreationErrorKind.GIT_AUTH_REQUIRED + return CreationErrorKind.GITHUB_AUTH_REQUIRED def _redact_url_credentials(url: str) -> str: diff --git a/apps/minds/imbue/minds/desktop_client/agent_creator_test.py b/apps/minds/imbue/minds/desktop_client/agent_creator_test.py index 6c6a4c5f82..ac45c6eee1 100644 --- a/apps/minds/imbue/minds/desktop_client/agent_creator_test.py +++ b/apps/minds/imbue/minds/desktop_client/agent_creator_test.py @@ -156,7 +156,7 @@ def test_classify_creation_error_flags_any_failed_github_clone() -> None: "Could not resolve host: github.com", ) for message in failures: - assert classify_creation_error(url, GitCloneError(message)) is CreationErrorKind.GIT_AUTH_REQUIRED, message + assert classify_creation_error(url, GitCloneError(message)) is CreationErrorKind.GITHUB_AUTH_REQUIRED, message def test_classify_creation_error_ignores_non_github_sources() -> None: diff --git a/apps/minds/imbue/minds/desktop_client/api_models.py b/apps/minds/imbue/minds/desktop_client/api_models.py index bdb083a10e..84bcb8960b 100644 --- a/apps/minds/imbue/minds/desktop_client/api_models.py +++ b/apps/minds/imbue/minds/desktop_client/api_models.py @@ -82,7 +82,7 @@ class CreateOperationStatusResponse(FrozenModel): error_kind: str | None = Field( default=None, description=( - "Machine-readable failure classification (e.g. GIT_AUTH_REQUIRED), set when the " + "Machine-readable failure classification (e.g. GITHUB_AUTH_REQUIRED), set when the " "failure is recognized; the creating page gates extra static guidance on it" ), ) diff --git a/apps/minds/imbue/minds/desktop_client/api_v1.py b/apps/minds/imbue/minds/desktop_client/api_v1.py index bb3e36f065..e65a7aca3c 100644 --- a/apps/minds/imbue/minds/desktop_client/api_v1.py +++ b/apps/minds/imbue/minds/desktop_client/api_v1.py @@ -1015,7 +1015,7 @@ def _handle_create_operation_status(operation_id: str) -> CreateOperationStatusR # redirects without reconstructing it client-side. redirect_url=info.redirect_url, error=info.error, - # Machine-readable failure classification (e.g. GIT_AUTH_REQUIRED for a + # Machine-readable failure classification (e.g. GITHUB_AUTH_REQUIRED for a # private/nonexistent GitHub repo); the creating page reveals static # guidance for kinds it knows about. error_kind=str(info.error_kind) if info.error_kind is not None else None, diff --git a/apps/minds/imbue/minds/desktop_client/api_v1_test.py b/apps/minds/imbue/minds/desktop_client/api_v1_test.py index bf2df4b9b1..881e25db33 100644 --- a/apps/minds/imbue/minds/desktop_client/api_v1_test.py +++ b/apps/minds/imbue/minds/desktop_client/api_v1_test.py @@ -541,7 +541,7 @@ def test_create_operation_status_carries_error_kind_for_classified_failures( status=AgentCreationStatus.FAILED, launch_mode=LaunchMode.DOCKER, error="git clone failed:\nfatal: could not read Username for 'https://github.com'", - error_kind=CreationErrorKind.GIT_AUTH_REQUIRED, + error_kind=CreationErrorKind.GITHUB_AUTH_REQUIRED, ), ) client = _client_with_agent_creator( @@ -554,7 +554,7 @@ def test_create_operation_status_carries_error_kind_for_classified_failures( body = json.loads(response.data) assert body["status"] == "FAILED" assert body["error"] - assert body["error_kind"] == "GIT_AUTH_REQUIRED" + assert body["error_kind"] == "GITHUB_AUTH_REQUIRED" def test_create_workspace_full_surface_returns_202_and_threads_fields( diff --git a/apps/minds/imbue/minds/desktop_client/static/creating.js b/apps/minds/imbue/minds/desktop_client/static/creating.js index 6d533f52e1..43729c4191 100644 --- a/apps/minds/imbue/minds/desktop_client/static/creating.js +++ b/apps/minds/imbue/minds/desktop_client/static/creating.js @@ -72,8 +72,8 @@ // Reveal extra static guidance for recognized failure kinds (currently // just the private-GitHub-repo case). The copy lives hidden in the // template; the backend only classifies. - if (creationErrorKind === 'GIT_AUTH_REQUIRED') { - var authHelp = document.getElementById('git-auth-help'); + if (creationErrorKind === 'GITHUB_AUTH_REQUIRED') { + var authHelp = document.getElementById('github-auth-help'); if (authHelp) authHelp.classList.remove('hidden'); } // The prominent error box now carries the message, so clear the faint diff --git a/apps/minds/imbue/minds/desktop_client/templates/pages/Creating.jinja b/apps/minds/imbue/minds/desktop_client/templates/pages/Creating.jinja index 4cf2a2851b..1520eeef89 100644 --- a/apps/minds/imbue/minds/desktop_client/templates/pages/Creating.jinja +++ b/apps/minds/imbue/minds/desktop_client/templates/pages/Creating.jinja @@ -43,11 +43,11 @@ {# Private-repo guidance: revealed by creating.js only when the backend #} - {# classifies the failure as GIT_AUTH_REQUIRED (the workspace source is #} - {# a github.com URL none of this computer's git credentials can see). #} + {# classifies the failure as GITHUB_AUTH_REQUIRED (the workspace source #} + {# is a github.com URL none of this computer's git credentials can see). #} {# The copy is static template content gated on the error kind; the #} {# backend only classifies, it never generates prose. #} -