Skip to content

Runner: SSH identity on Sergeys-Mac-mini resolves to a deploy key, breaking git clone over SSH #684

Closed

Description

@serge-ivo

Problem

On the runner node Sergeys-Mac-mini.local, the SSH key presented to GitHub authenticates as jobsearch-works/shared — a repository deploy key, not the user's account. Any git clone git@github.com:... for a private repo outside that one repository therefore fails with "Repository not found", which is GitHub's generic message for "authenticated, but this identity cannot see it". The failure is misleading: it looks like the repo is missing when it is really a wrong-identity problem.

Observed

Cloning proappstore-online/school-clubs over SSH failed this way. The same clone succeeded immediately via gh repo clone over HTTPS, which uses GITHUB_TOKEN and authenticates as serge-ivo, an identity that can see the repo.

Consequence

Two different GitHub identities are active on the same machine depending on transport: a narrow deploy key over SSH and a broad user token over HTTPS. Any coding run that happens to use an SSH remote will silently get the wrong, much weaker identity. Existing checkouts with SSH remotes may fail to fetch or push for reasons that appear to be missing repositories.

Suggested Work

  • Audit which identity each transport resolves to on every registered runner node.
  • Decide and document the intended identity for agent git operations.
  • Surface the effective git identity in coding_diagnostics so this is visible without a failed clone, and consider reporting it per transport.
  • Where a deploy key is genuinely wanted, scope it to the repo that needs it rather than leaving it as the default SSH identity.

Related

This is adjacent to the credential-scope concerns in #676: the credential an agent actually operates with is not currently visible before it is used.

Cross-reference #676.

Activity

  1. added
    P2: correctnessReal defect, no live harm today — inert fields, miscounts, missing guards
    bugSomething isn't working
    securitySecurity hardening / audit finding
    runnerpackages/browser-runner + packages/cli — ships only via an npm publish
    coderThe Coder wedge agent (#68) — Engine, Pilot, Co-pilot, Loop, Overseer
    on Aug 18, 2026
  2. serge-ivo commented on Aug 29, 2026

    @serge-ivo
    ContributorAuthor

    Code fix shipped — commit a1b71d5

    What was implemented

    Runner (packages/browser-runner/src/coding/repo.ts):

    • Added parseSshIdentity(raw) — pure function that parses the GitHub SSH welcome banner (Hi <name>! You've...) to extract the authenticating identity. Deploy keys arrive as <org>/<repo> (a slash is the structural marker); user accounts are bare login names. 5 unit tests pin the discriminator.
    • Added probeGitSshIdentity(host) — calls ssh -T -o BatchMode=yes -o ConnectTimeout=5 git@<host> and returns { checked, host, identity, isDeployKey, raw }.

    Runner server (packages/browser-runner/src/server.ts):

    • Added /coding/git-identity endpoint — calls probeGitSshIdentity("github.com") and returns the result.

    API diagnostics (workers/api/src/routes/coding-diagnostics.ts):

    • coding_diagnostics now calls /coding/git-identity on the runner (alongside /health and /coding/diagnostics) and includes the result as gitIdentity in the response.
    • When isDeployKey: true + SSH repos exist → emits a warn issue naming the affected repos and explaining that a deploy key authenticates to exactly one repository.
    • When authentication failed entirely + SSH repos exist → emits a warn issue.
    • The probe only runs when a runner is connected and is a real SSH connection to github.com, so instances with no SSH repos see no added latency.

    Owner machine action still required

    The code fix makes the problem visible without a failed clone. The underlying cause on Sergeys-Mac-mini.local is that ~/.ssh/config routes github.com to a deploy key for jobsearch-works/shared rather than to the user account serge-ivo. Opening coding_diagnostics for any instance on that machine (with SSH-URL repos) will now show:

    SSH identity on this machine is a deploy key (jobsearch-works/shared) — not a user account. Repos cloning over SSH: "...". A deploy key authenticates to exactly one repository; private repos outside that one will fail with "Repository not found".

    To fix the machine: check ~/.ssh/config for a Host github.com stanza that points to the deploy key. Either remove it (if the deploy key is no longer needed), add a Host github-deploy alias for it (so github.com resolves to the user key), or re-add the affected repos in the Coder console using HTTPS clone URLs so the platform can inject a token.

    Closes #684 for the code half. Machine config fix is out of scope for a code push.

  3. serge-ivo commented on Sep 19, 2026

    @serge-ivo
    ContributorAuthor

    Closing — three of four Suggested Work items delivered; the fourth is machine config

    Closing on the orchestrator's instruction. Reopen freely if the last item below should keep
    this ticket alive
    — the machine that named this issue has not been touched, and I could not
    verify it from here.

    What landed this session

    Auditing the existing fix (a1b71d51) against main turned up a defect in it, so there are
    three commits rather than one.

    Commit What
    b3f6617e Probe the host each repo actually clones from, not an assumed github.com
    09553008 Fold the HTTPS identity into the same report, so both transports are visible together
    5846af1a platform-docs/git-identity.md — the intended identity, recorded

    The defect in the original fix

    a1b71d51 probed the right thing and asked the wrong host. The cloud called
    /coding/git-identity with no body, the runner defaulted to github.com, and that single
    verdict was applied to every repo whose clone URL merely began with git@. Nothing anywhere
    derived the host from a repo's own URL — the endpoint had accepted a host parameter since
    a1b71d51, and nothing ever passed one.

    An SSH host alias is the ordinary two-account setup, so one line produced both errors:

    • False positive, reproduced on a real machine: ssh -T git@github.com returns
      Permission denied (publickey) there, while the platform monorepo pushes fine over
      git@github-personal. The check warned that "private repo clones will fail" about a
      machine that was pushing successfully. A diagnostics page that cries wolf is worse than one
      that says nothing.
    • False negative — this issue's own bug, one alias away. A deploy key on
      git@github-work:… while github.com happened to resolve to a user account emitted no
      issue at all
      . The check passed while the transport it claimed to have checked was broken.

    The replaced logic had no test, which is why neither was caught. Grouping and outcomes are
    now pure in workers/api/src/lib/ssh-identity.ts; both failure modes are named cases among the
    19 tests.

    Two claims in the earlier comment also did not hold and now do: the probe ran
    unconditionally, not only when SSH repos existed (it now derives hosts from repos, so no SSH
    repos means no probe), and the warn text hardcoded github.com regardless of the host
    measured.

    Against the four Suggested Work items

    Item Status
    Audit which identity each transport resolves to on every registered runner node Partial. Both transports are audited per instance, on demand. Not a sweep across all registered nodes — that would be a scheduled job, and wants its own ticket if wanted
    Decide and document the intended identity Done — platform-docs/git-identity.md
    Surface the effective git identity in coding_diagnostics, "and consider reporting it per transport" Done — gitIdentities (per SSH host, with its repos) + githubCredentials (the gh login), probed together
    Scope a deploy key to the repo that needs it Not done — machine config on Sergeys-Mac-mini.local, unchanged since this was filed

    What the report now says

    • warn — deploy key on a host, naming only that host's repos, and naming the account HTTPS
      would use instead when the gh login is known. That is this issue's own observation
      (gh repo clone over HTTPS cloned what SSH had just refused) turned into the remedy text.
    • warn — the SSH handshake to a host did not authenticate.
    • info — SSH and HTTPS resolve to different accounts. Informational by design, and
      excluded from issueCount: two accounts behind two ~/.ssh/config aliases is the ordinary way
      to hold both on one machine and necessarily disagrees with the single gh login. Warning about
      it would be the false-positive class above, one layer up.
    • Nothing for a host that could not be measured. Unverified is not a finding.

    Two things I did not do

    1. The machine. Sergeys-Mac-mini.local's ~/.ssh/config still routes github.com to the
      jobsearch-works/shared deploy key as far as anyone knows — I ran the session on a different
      machine and could not check. The new doc's "Pin the SSH host to one identity file" section
      is what that machine needs, and ssh -T git@github.com there is the one-line check. The
      diagnostics will now name it correctly per host if it is still wrong.
    2. A step list for that machine, in the published docs. platform-docs/ ships to every PAGS
      user, so the page is written as reference for any runner operator. Per-machine remediation is
      a walkthrough, not a document.

    The doc leans hardest on IdentitiesOnly yes, which is the directive most often omitted and the
    reason this class of bug is non-local: without it SSH offers every agent key in turn and
    authenticates as whichever GitHub accepts first, so adding an unrelated deploy key can
    silently change the identity an existing, working checkout uses. IdentityFile alone does not
    prevent it.

    Verified: 11,900 tests, tsc --noEmit (api + workspace), biome over 1,474 files,
    docs:drift, docs:build --strict, check-doc-citations, and eight further check scripts —
    all green on each of the three commits.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2: correctnessReal defect, no live harm today — inert fields, miscounts, missing guardsbugSomething isn't workingcoderThe Coder wedge agent (#68) — Engine, Pilot, Co-pilot, Loop, Overseerrunnerpackages/browser-runner + packages/cli — ships only via an npm publishsecuritySecurity hardening / audit finding

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions