project_create fails at step create_repo with mcp http 401: unauthorized when called via the routing pod. The routing pod cannot authenticate against the gitea-mcp server.
If it's a placeholder, the real token lives elsewhere (SOPS encrypted file, .env on disk, etc).
Question 3: How does claude.ai authenticate to gitea-mcp today?
claude.ai calls gitea-mcp successfully (we use it in every session). Check what auth mechanism it uses — likely Dex JWT, not static token. If so, routing pod needs to use Dex JWT too, not a static token.
Check gitea-mcp auth middleware:
cat ~/dev/gitea-mcp/internal/auth/
Question 4: What does the mcpclient in hyperguild send as auth?
The mcpclient.New(cfg.GiteaMCPURL, cfg.GiteaMCPToken) call — what does it set as the auth header? Does it match what gitea-mcp expects?
Expected fix
Once the correct token/auth mechanism is identified:
If static token: extract real value from SOPS/secrets, patch into routing-secrets correctly
If Dex JWT: routing pod needs to obtain a JWT token to call gitea-mcp — may need a service account or client credentials flow
If no auth required for internal cluster calls: check if gitea-mcp has an internal (no-auth) endpoint on a different port
After fix
Re-run e2e test:
# From claude.ai session or direct curl:
routing:project_create(name="test-throwaway-003",
description="E2E test",
hypothesis="...",
stack="go-web",
folder="AGENTS",
private=true)
Expected: all five artifacts created — Gitea repo, GitHub repo, mirror sync, infra namespace, issue.
Cleanup needed
test-throwaway-001 and test-throwaway-002 Gitea repos still exist and should be deleted after this is resolved. GitHub repos too if they were created.
infra commits: 92cdeccf, b6d8244, fd3d4d1 (deployment.yaml patches)
## Problem
`project_create` fails at step `create_repo` with `mcp http 401: unauthorized` when called via the routing pod. The routing pod cannot authenticate against the gitea-mcp server.
## Symptoms
```
{"code": -32000, "message": "project_create step \"create_repo\" failed: mcp http 401: unauthorized"}
```
## Investigation so far (2026-05-18)
### What we know
- routing pod is running and healthy
- `GITEA_MCP_TOKEN` was patched into `routing-secrets` but appears to be **empty**
- `GITEA_MCP_STATIC_TOKEN` in `gitea-mcp-secrets` appears to be a placeholder value (`---...`)
- gitea-mcp logs show no auth errors — only startup message visible
- gitea-mcp has been running since `2026-05-18T06:06:42Z` with no restarts
### What needs investigation
**Question 1: What is GITEA_MCP_STATIC_TOKEN actually used for?**
gitea-mcp has two token env vars:
- `GITEA_MCP_DEFAULT_TOKEN` — likely the Gitea API token (for API calls to Gitea)
- `GITEA_MCP_STATIC_TOKEN` — likely the MCP server auth token (for authenticating MCP clients)
Verify by reading gitea-mcp source:
```bash
grep -r "STATIC_TOKEN\|static_token\|StaticToken" ~/dev/gitea-mcp/
grep -r "GITEA_MCP_AUTH\|MCP_AUTH\|bearer\|Bearer" ~/dev/gitea-mcp/internal/auth/
```
**Question 2: Is the GITEA_MCP_STATIC_TOKEN a real value or placeholder?**
```bash
kubectl get secret -n gitea-mcp gitea-mcp-secrets \
-o jsonpath='{.data}' | jq 'to_entries[] | {key: .key, value: (.value | @base64d)}'
```
If it's a placeholder, the real token lives elsewhere (SOPS encrypted file, .env on disk, etc).
**Question 3: How does claude.ai authenticate to gitea-mcp today?**
claude.ai calls gitea-mcp successfully (we use it in every session). Check what auth mechanism it uses — likely Dex JWT, not static token. If so, routing pod needs to use Dex JWT too, not a static token.
Check gitea-mcp auth middleware:
```bash
cat ~/dev/gitea-mcp/internal/auth/
```
**Question 4: What does the mcpclient in hyperguild send as auth?**
```bash
cat ~/dev/hyperguild/internal/mcpclient/client.go | grep -i "auth\|token\|bearer\|header"
```
The `mcpclient.New(cfg.GiteaMCPURL, cfg.GiteaMCPToken)` call — what does it set as the auth header? Does it match what gitea-mcp expects?
## Expected fix
Once the correct token/auth mechanism is identified:
1. If static token: extract real value from SOPS/secrets, patch into `routing-secrets` correctly
2. If Dex JWT: routing pod needs to obtain a JWT token to call gitea-mcp — may need a service account or client credentials flow
3. If no auth required for internal cluster calls: check if gitea-mcp has an internal (no-auth) endpoint on a different port
## After fix
Re-run e2e test:
```bash
# From claude.ai session or direct curl:
routing:project_create(
name="test-throwaway-003",
description="E2E test",
hypothesis="...",
stack="go-web",
folder="AGENTS",
private=true
)
```
Expected: all five artifacts created — Gitea repo, GitHub repo, mirror sync, infra namespace, issue.
## Cleanup needed
`test-throwaway-001` and `test-throwaway-002` Gitea repos still exist and should be deleted after this is resolved. GitHub repos too if they were created.
## Related
- hyperguild #11 (e2e findings)
- hyperguild #12 (GITHUB_PAT config — resolved)
- infra commits: `92cdeccf`, `b6d8244`, `fd3d4d1` (deployment.yaml patches)
routing-secrets did not contain GITEA_MCP_TOKEN. The mcpclient.New(url, token) constructor accepts an empty token without complaint, and at request time it only sets Authorization: Bearer … when c.token != "". So the routing pod was sending requests with no Authorization header at all — gitea-mcp's BearerMiddleware rejects that outright as 401.
Answers to the investigation questions
GITEA_MCP_STATIC_TOKEN is the MCP server auth token, compared constant-time against the incoming Bearer in internal/auth/bearer.go. GITEA_MCP_DEFAULT_TOKEN is the upstream Gitea PAT used by the gitea client. They're intentionally decoupled (see BearerMiddleware doc comment).
Real value, not placeholder — d7423fc3eabcb9d2f1ee2b0d88b5d15a2c51b54f in gitea-mcp/secrets.enc.yaml.
claude.ai uses Dex JWT (the else branch of the middleware). Routing pod doesn't have a JWT-issuing identity inside the cluster, so static token is the correct choice for service-to-service.
mcpclient sends Authorization: Bearer <c.token> only when token is non-empty. Empty c.token ⇒ no header ⇒ 401. Matches what gitea-mcp expects when the value is the static token.
Fix
infra commit 2b7d792:
sops setGITEA_MCP_TOKEN: d7423fc3… into routing-secrets.
Bumped pod-template secrets-revision annotation so the rollout picked up the new envFrom value (envFrom doesn't auto-reload on Secret changes — known gotcha).
Verification
E2E project_create(name=test-throwaway-003, …) via port-forward to routing/3210:
staging/test-throwaway-003 branch on infra deleted ✅
GitHub side: mathiasb/test-throwaway-003 still orphaned — gh token lacks delete_repo scope. Run gh auth refresh -h github.com -s delete_repo then gh repo delete mathiasb/test-throwaway-003 --yes. (-001 and -002 were 404 — never created on GitHub because GITHUB_PAT was missing during those runs.)
Closing.
## Resolved
### Root cause
`routing-secrets` did not contain `GITEA_MCP_TOKEN`. The `mcpclient.New(url, token)` constructor accepts an empty token without complaint, and at request time it only sets `Authorization: Bearer …` when `c.token != ""`. So the routing pod was sending requests **with no Authorization header at all** — gitea-mcp's `BearerMiddleware` rejects that outright as 401.
### Answers to the investigation questions
1. **`GITEA_MCP_STATIC_TOKEN`** is the MCP server auth token, compared constant-time against the incoming Bearer in `internal/auth/bearer.go`. `GITEA_MCP_DEFAULT_TOKEN` is the upstream Gitea PAT used by the gitea client. They're intentionally decoupled (see `BearerMiddleware` doc comment).
2. **Real value, not placeholder** — `d7423fc3eabcb9d2f1ee2b0d88b5d15a2c51b54f` in `gitea-mcp/secrets.enc.yaml`.
3. **claude.ai uses Dex JWT** (the `else` branch of the middleware). Routing pod doesn't have a JWT-issuing identity inside the cluster, so static token is the correct choice for service-to-service.
4. **mcpclient sends `Authorization: Bearer <c.token>`** only when token is non-empty. Empty `c.token` ⇒ no header ⇒ 401. Matches what gitea-mcp expects when the value is the static token.
### Fix
infra commit `2b7d792`:
- `sops set` `GITEA_MCP_TOKEN: d7423fc3…` into `routing-secrets`.
- Bumped pod-template `secrets-revision` annotation so the rollout picked up the new `envFrom` value (envFrom doesn't auto-reload on Secret changes — known gotcha).
### Verification
E2E `project_create(name=test-throwaway-003, …)` via port-forward to `routing/3210`:
```
"reached":["create_repo","create_github_repo","mirror","infra_commit","issue"]
```
All five steps green.
### Cleanup
- `mathias/test-throwaway-002`, `mathias/test-throwaway-003` deleted ✅
- `staging/test-throwaway-003` branch on infra deleted ✅
- GitHub side: `mathiasb/test-throwaway-003` still orphaned — `gh` token lacks `delete_repo` scope. Run `gh auth refresh -h github.com -s delete_repo` then `gh repo delete mathiasb/test-throwaway-003 --yes`. (`-001` and `-002` were 404 — never created on GitHub because GITHUB_PAT was missing during those runs.)
Closing.
Root cause: GITEA_MCP_TOKEN in routing-secrets was empty. Fixed by CC on koala — correct static token patched into routing-secrets and routing pod restarted.
project_create e2e test passed after fix. All five steps reached including create_repo which was previously 401.
Remaining action
GITEA_MCP_TOKEN should be added to the SOPS-encrypted secrets.enc.yaml for the routing pod so it survives secret rotation and cluster rebuilds — currently only patched imperatively into routing-secrets.
## ✅ RESOLVED — 2026-05-18
Root cause: `GITEA_MCP_TOKEN` in routing-secrets was empty. Fixed by CC on koala — correct static token patched into routing-secrets and routing pod restarted.
project_create e2e test passed after fix. All five steps reached including `create_repo` which was previously 401.
## Remaining action
GITEA_MCP_TOKEN should be added to the SOPS-encrypted `secrets.enc.yaml` for the routing pod so it survives secret rotation and cluster rebuilds — currently only patched imperatively into routing-secrets.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
project_createfails at stepcreate_repowithmcp http 401: unauthorizedwhen called via the routing pod. The routing pod cannot authenticate against the gitea-mcp server.Symptoms
Investigation so far (2026-05-18)
What we know
GITEA_MCP_TOKENwas patched intorouting-secretsbut appears to be emptyGITEA_MCP_STATIC_TOKENingitea-mcp-secretsappears to be a placeholder value (---...)2026-05-18T06:06:42Zwith no restartsWhat needs investigation
Question 1: What is GITEA_MCP_STATIC_TOKEN actually used for?
gitea-mcp has two token env vars:
GITEA_MCP_DEFAULT_TOKEN— likely the Gitea API token (for API calls to Gitea)GITEA_MCP_STATIC_TOKEN— likely the MCP server auth token (for authenticating MCP clients)Verify by reading gitea-mcp source:
Question 2: Is the GITEA_MCP_STATIC_TOKEN a real value or placeholder?
If it's a placeholder, the real token lives elsewhere (SOPS encrypted file, .env on disk, etc).
Question 3: How does claude.ai authenticate to gitea-mcp today?
claude.ai calls gitea-mcp successfully (we use it in every session). Check what auth mechanism it uses — likely Dex JWT, not static token. If so, routing pod needs to use Dex JWT too, not a static token.
Check gitea-mcp auth middleware:
Question 4: What does the mcpclient in hyperguild send as auth?
The
mcpclient.New(cfg.GiteaMCPURL, cfg.GiteaMCPToken)call — what does it set as the auth header? Does it match what gitea-mcp expects?Expected fix
Once the correct token/auth mechanism is identified:
routing-secretscorrectlyAfter fix
Re-run e2e test:
Expected: all five artifacts created — Gitea repo, GitHub repo, mirror sync, infra namespace, issue.
Cleanup needed
test-throwaway-001andtest-throwaway-002Gitea repos still exist and should be deleted after this is resolved. GitHub repos too if they were created.Related
92cdeccf,b6d8244,fd3d4d1(deployment.yaml patches)Resolved
Root cause
routing-secretsdid not containGITEA_MCP_TOKEN. Themcpclient.New(url, token)constructor accepts an empty token without complaint, and at request time it only setsAuthorization: Bearer …whenc.token != "". So the routing pod was sending requests with no Authorization header at all — gitea-mcp'sBearerMiddlewarerejects that outright as 401.Answers to the investigation questions
GITEA_MCP_STATIC_TOKENis the MCP server auth token, compared constant-time against the incoming Bearer ininternal/auth/bearer.go.GITEA_MCP_DEFAULT_TOKENis the upstream Gitea PAT used by the gitea client. They're intentionally decoupled (seeBearerMiddlewaredoc comment).d7423fc3eabcb9d2f1ee2b0d88b5d15a2c51b54fingitea-mcp/secrets.enc.yaml.elsebranch of the middleware). Routing pod doesn't have a JWT-issuing identity inside the cluster, so static token is the correct choice for service-to-service.Authorization: Bearer <c.token>only when token is non-empty. Emptyc.token⇒ no header ⇒ 401. Matches what gitea-mcp expects when the value is the static token.Fix
infra commit
2b7d792:sops setGITEA_MCP_TOKEN: d7423fc3…intorouting-secrets.secrets-revisionannotation so the rollout picked up the newenvFromvalue (envFrom doesn't auto-reload on Secret changes — known gotcha).Verification
E2E
project_create(name=test-throwaway-003, …)via port-forward torouting/3210:All five steps green.
Cleanup
mathias/test-throwaway-002,mathias/test-throwaway-003deleted ✅staging/test-throwaway-003branch on infra deleted ✅mathiasb/test-throwaway-003still orphaned —ghtoken lacksdelete_reposcope. Rungh auth refresh -h github.com -s delete_repothengh repo delete mathiasb/test-throwaway-003 --yes. (-001and-002were 404 — never created on GitHub because GITHUB_PAT was missing during those runs.)Closing.
✅ RESOLVED — 2026-05-18
Root cause:
GITEA_MCP_TOKENin routing-secrets was empty. Fixed by CC on koala — correct static token patched into routing-secrets and routing pod restarted.project_create e2e test passed after fix. All five steps reached including
create_repowhich was previously 401.Remaining action
GITEA_MCP_TOKEN should be added to the SOPS-encrypted
secrets.enc.yamlfor the routing pod so it survives secret rotation and cluster rebuilds — currently only patched imperatively into routing-secrets.