krz/gitbay

A CLI-first git forge.

clone: git clone https://gitbay.org/krz/gitbay.git

0d422fbdaf6144ce5f971d8210e4b8123f3bdad4

verified · cmc

author: Christian Cleberg <hello@cleberg.net> · 2026-08-24T02:31:43Z

cli: foreign origins must not hijack instance resolution

Resolution order is now configured-instance match (with repo
inference), then the default instance (no inference), then the raw
origin as an ad-hoc instance only when nothing is configured. Running
gitbay inside a github clone previously sshed to github and got
git-shell errors. Repo inference only applies when the origin matches
the chosen instance.
 cmd/gitbay/ssh.go | 43 ++++++++++++++++++++++---------------------
 e2e/cli_test.go   | 16 ++++++++++++++++
 2 files changed, 38 insertions(+), 21 deletions(-)

diff --git a/cmd/gitbay/ssh.go b/cmd/gitbay/ssh.go
index 53961b2..5025dc5 100644
--- a/cmd/gitbay/ssh.go
+++ b/cmd/gitbay/ssh.go
@@ -20,39 +20,40 @@ type target struct {
 	repo string // owner/name, "" when not inferable
 }
 
-// resolveTarget picks the instance and repo. Inside a git repo whose origin
-// remote points at a configured (or any ssh) forge host, that wins;
-// otherwise the configured default instance.
+// resolveTarget picks the instance and repo. An origin remote matching a
+// CONFIGURED instance wins and carries repo inference; otherwise the
+// default instance is used with no inference — a clone from some other
+// host (github, a different forge) must never hijack the command. The
+// raw origin serves as an ad-hoc instance only when nothing is configured
+// at all.
 func resolveTarget() (target, error) {
 	cfg, err := cliconfig.Load()
 	if err != nil {
 		return target{}, err
 	}
 
-	if url := originURL(); url != "" {
-		if parsed, repo, ok := cliconfig.ParseRemoteURL(url); ok {
-			// Prefer a configured instance for the same host+port: it may
-			// carry ssh_options the bare URL cannot express.
-			norm := func(p int) int {
-				if p == 0 {
-					return 22
-				}
-				return p
+	parsed, repo, originOK := cliconfig.ParseRemoteURL(originURL())
+	if originOK {
+		norm := func(p int) int {
+			if p == 0 {
+				return 22
 			}
-			for _, inst := range cfg.Instances {
-				if inst.Host == parsed.Host && norm(inst.Port) == norm(parsed.Port) {
-					return target{inst: inst, repo: repo}, nil
-				}
+			return p
+		}
+		for _, inst := range cfg.Instances {
+			if inst.Host == parsed.Host && norm(inst.Port) == norm(parsed.Port) {
+				return target{inst: inst, repo: repo}, nil
 			}
-			return target{inst: parsed, repo: repo}, nil
 		}
 	}
 
-	inst, _, err := cfg.DefaultInstance()
-	if err != nil {
-		return target{}, err
+	if inst, _, err := cfg.DefaultInstance(); err == nil {
+		return target{inst: inst}, nil
+	}
+	if originOK {
+		return target{inst: parsed, repo: repo}, nil
 	}
-	return target{inst: inst}, nil
+	return target{}, fmt.Errorf("no gitbay instance configured; run: gitbay remote add <name> <host>")
 }
 
 func originURL() string {
diff --git a/e2e/cli_test.go b/e2e/cli_test.go
index 9d5073f..ee387eb 100644
--- a/e2e/cli_test.go
+++ b/e2e/cli_test.go
@@ -193,6 +193,22 @@ func TestCLI(t *testing.T) {
 		t.Fatalf("init-created repo: %s", out)
 	}
 
+	// A clone whose origin points at a FOREIGN host must not hijack the
+	// command: the configured default instance is used, and repo inference
+	// is dropped rather than guessed across hosts.
+	foreign := filepath.Join(t.TempDir(), "f")
+	os.MkdirAll(foreign, 0o755)
+	mustGit(t, foreign, cliGitEnv, "init", "-q")
+	mustGit(t, foreign, cliGitEnv, "remote", "add", "origin", "git@github.example:someone/thing.git")
+	out = c.must(t, foreign, "", "auth", "whoami")
+	if strings.TrimSpace(out) != "alice" {
+		t.Fatalf("foreign-origin whoami hit the wrong host: %q", out)
+	}
+	_, errOut2, code2 := c.run(t, foreign, "", "issue", "list")
+	if code2 != 2 || !strings.Contains(errOut2, "none inferable") {
+		t.Fatalf("foreign-origin repo inference: exit %d, %s", code2, errOut2)
+	}
+
 	// Man pages and completions generate.
 	manDir := t.TempDir()
 	c.must(t, "", "", "man", "--dir", manDir)