diff --git a/CHANGELOG.md b/CHANGELOG.md
index 64ed5b606..69afc7860 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - `apm install --global` now installs MCP servers to global-capable runtimes (Copilot CLI, Codex CLI) instead of blanket-skipping all MCP installation at user scope. Note: lockfile-path behavior at `--global` tracked in #794 (#638)
 - `--trust-transitive-mcp` no longer silently ignored when combined with `--global` (#638)
 - Token resolution now discriminates by port, fixing credential collisions across multiple self-hosted Git instances on the same host. Thanks @edenfunf! (#785)
+- Fix `apm init` showing overwrite confirmation prompt three times on Windows CP950 terminals (#602)
 
 ## [0.8.12] - 2026-04-19
 
diff --git a/src/apm_cli/commands/init.py b/src/apm_cli/commands/init.py
index f97ff46df..f1b7cdd57 100644
--- a/src/apm_cli/commands/init.py
+++ b/src/apm_cli/commands/init.py
@@ -19,7 +19,6 @@
     _create_plugin_json,
     _get_console,
     _get_default_config,
-    _lazy_confirm,
     _rich_blank_line,
     _validate_plugin_name,
     _validate_project_name,
@@ -84,14 +83,7 @@ def init(ctx, project_name, yes, plugin, verbose):
             logger.warning("apm.yml already exists")
 
             if not yes:
-                Confirm = _lazy_confirm()
-                if Confirm:
-                    try:
-                        confirm = Confirm.ask("Continue and overwrite?")
-                    except Exception:
-                        confirm = click.confirm("Continue and overwrite?")
-                else:
-                    confirm = click.confirm("Continue and overwrite?")
+                confirm = click.confirm("Continue and overwrite?")
 
                 if not confirm:
                     logger.progress("Initialization cancelled.")
diff --git a/tests/unit/test_init_command.py b/tests/unit/test_init_command.py
index 135d91f02..cabde1069 100644
--- a/tests/unit/test_init_command.py
+++ b/tests/unit/test_init_command.py
@@ -220,6 +220,51 @@ def test_init_existing_project_interactive_cancel(self):
             finally:
                 os.chdir(self.original_dir)  # restore CWD before TemporaryDirectory cleanup
 
+    def test_init_existing_project_confirm_prompt_shown_once(self):
+        """Test that overwrite confirmation prompt appears exactly once (#602).
+
+        On Windows CP950 terminals, Rich Confirm.ask() could fail on encoding,
+        retry internally, then fall back to click.confirm(), showing the prompt
+        three times. After the fix, only click.confirm() is used.
+        """
+        with tempfile.TemporaryDirectory() as tmp_dir:
+            os.chdir(tmp_dir)
+            try:
+
+                # Create existing apm.yml
+                Path("apm.yml").write_text("name: existing-project\nversion: 0.1.0\n")
+
+                # Say yes to overwrite, then provide interactive setup input
+                user_input = "y\nmy-project\n1.0.0\nA description\nAuthor\ny\n"
+                result = self.runner.invoke(cli, ["init"], input=user_input)
+
+                assert result.exit_code == 0
+                # The overwrite prompt must appear exactly once
+                assert result.output.count("Continue and overwrite?") == 1
+            finally:
+                os.chdir(self.original_dir)  # restore CWD before TemporaryDirectory cleanup
+
+    def test_init_existing_project_confirm_uses_click(self):
+        """Test that overwrite confirmation uses click.confirm, not Rich (#602)."""
+        with tempfile.TemporaryDirectory() as tmp_dir:
+            os.chdir(tmp_dir)
+            try:
+
+                # Create existing apm.yml
+                Path("apm.yml").write_text("name: existing-project\nversion: 0.1.0\n")
+
+                with patch("apm_cli.commands.init.click.confirm", return_value=True) as mock_confirm:
+                    result = self.runner.invoke(cli, ["init", "--yes"])
+                    # --yes skips the prompt entirely, so confirm should NOT be called
+                    mock_confirm.assert_not_called()
+
+                with patch("apm_cli.commands.init.click.confirm", return_value=False) as mock_confirm:
+                    result = self.runner.invoke(cli, ["init"])
+                    mock_confirm.assert_called_once_with("Continue and overwrite?")
+                    assert "Initialization cancelled" in result.output
+            finally:
+                os.chdir(self.original_dir)  # restore CWD before TemporaryDirectory cleanup
+
     def test_init_validates_project_structure(self):
         """Test that init creates minimal project structure."""
         with tempfile.TemporaryDirectory() as tmp_dir:
