Let pushes go by an HTTPS token or an SSH deploy key
CI / checks (push) Skipped
CI / checks (pull_request) Failing after 56s

The push credential is either a push-only account's token, handed to git
through a credential helper that names environment variables, never
values, or a deploy key in a short-lived ssh-agent. The remote URL
follows the credential. Configured as gitea.push_user and push_token, or
gitea.push_key.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
This commit is contained in:
dongarandClaude Opus 5.5 committed 2026-10-02 21:59:56 +00:00
1 parent 761ebdb857
commit c931ac939b
15 files changed
+241 -81

No files matched your search

+25 -10
View File
@@ -21,7 +21,7 @@ from nisse_dev.delegation import Caps, Delegation
from nisse_dev.execution import SSHHost
from nisse_dev.executor import LLMExecutor
from nisse_dev.expert import ExpertDesk
from nisse_dev.git import GitRepo
from nisse_dev.git import GitRepo, HTTPSToken, PushCredential, SSHKey
from nisse_dev.gitea import Comment, GiteaClient
from nisse_dev.llm import AnthropicPassthroughClient, LiteLLMClient
from nisse_dev.matrix import MatrixTransport
@@ -177,30 +177,45 @@ def build_host(config: AgentConfig) -> SSHHost:
)
def read_push_key(config: AgentConfig) -> str | None:
def push_credential(config: AgentConfig) -> PushCredential | None:
"""What pushes go by: a push-only account's token over HTTPS, or a
deploy key over SSH (D4)."""
if config.push_user and config.push_token:
return HTTPSToken(config.push_user, config.push_token)
if config.push_key is None:
return None
try:
return config.push_key.read_text()
return SSHKey(config.push_key.read_text())
except OSError as exc:
raise RuntimeError(
f"cannot read the push key {config.push_key}: {exc}"
) from exc
def remote_url(gitea: GiteaClient, repo: str, credential: PushCredential | None) -> str:
"""The credential-free URL the workspace clones from and pushes to."""
if isinstance(credential, SSHKey):
return gitea.ssh_url(repo)
return gitea.clone_url(repo)
def prepare_workspace(
host: SSHHost, config: AgentConfig, account: str, remote: str, key: str | None
host: SSHHost,
config: AgentConfig,
account: str,
remote: str,
credential: PushCredential | None,
) -> GitRepo:
"""Ensure the workspace on the build VM is a clone of the project repo.
The clone uses the repository's SSH URL, so no credential is ever stored
in it (#89); the push key is handed to the clone and nothing else.
in it (#89); the push credential is handed to the clone and nothing else.
Cloning here rather than when a work item starts is the single-project
shortcut that M1.4 and D3's fresh workspace replace.
"""
repo = GitRepo(host, config.workspace)
if not repo.exists():
repo.clone(remote, key=key)
repo.clone(remote, credential=credential)
repo.configure_identity(account, f"{account}@users.noreply.local")
return repo
@@ -229,9 +244,9 @@ def build_agent(
account = gitea.whoami()
host = build_host(config)
push_key = read_push_key(config)
remote = gitea.ssh_url(config.gitea_repo)
git_repo = prepare_workspace(host, config, account, remote, push_key)
credential = push_credential(config)
remote = remote_url(gitea, config.gitea_repo, credential)
git_repo = prepare_workspace(host, config, account, remote, credential)
tuning = config.tuning
workspace = Workspace(
host,
@@ -278,7 +293,7 @@ def build_agent(
config.gitea_repo,
git_repo,
remote_url=remote,
push_key=push_key,
push_credential=credential,
base_branch=config.base_branch,
)