feat(config): per-repo Penpot team mapping (pin designer to a specific team, not list-and-guess) #255

Closed
opened 2026-04-21 19:08:22 +00:00 by claude-desktop · 0 comments
Collaborator

User story

As an operator running the designer fleet across multiple repos, I want each watched repo to pin its Penpot team in config, so that the designer never has to guess which team to work in — and so that charles/claude-hookspeon-manager, charles/proxmox-iac → some-other-team, etc., stays explicit and auditable rather than inferred from list_teams.

Today skills/design-implement.md step 2 just says "find-or-create the Penpot file in the team's mockup project", and the agent calls mcp__penpot__list_teams to pick one. On 2026-04-21, task ce585c20 stopped at that step with 10 turns and $0.54 spent — the agent read a transit-json-encoded team list (~u41e41004-…) and never proceeded to create anything. Pinning the team in config removes the guess entirely.

Acceptance criteria

Config schema

  • config/agents.json grows a top-level penpot block:
    {
      "penpot": {
        "base_url": "https://design.jacquin.app",
        "default_team_id": "41e41004-fcd0-8115-8007-cd4ef7a479dd",
        "team_by_repo": {
          "charles/claude-hooks": { "team_name": "peon-manager", "team_id": "<uuid>" },
          "charles/proxmox-iac":  { "team_name": "<other>",      "team_id": "<uuid>" }
        }
      }
    }
    
  • base_url replaces the hardcoded https://design.jacquin.app in apps/server/src/main.ts startup so non-LAN deployments don't have to patch source.
  • default_team_id is used as the fallback when a repo has no explicit entry (so new repos don't silently break the designer).
  • Missing penpot block at all = loader proceeds silently with null; penpot_mcp: true types continue to work but without per-repo pinning (legacy behavior).

Loader + runner wiring

  • webhook-config.ts parses + validates the penpot block. UUIDs must look like UUIDs; unknown keys under team_by_repo log a warning but don't fail the loader.
  • agent-runner.ts surfaces penpot_team_id + penpot_team_name in the task prompt envelope when dispatching a penpot_mcp agent, resolved from team_by_repo[task.repo] ?? { team_id: default_team_id }.
  • /foreman/config + /agents responses include the effective Penpot team for transparency (read-only).

Skill update

  • skills/design-implement.md step 2 changes from "find-or-create the Penpot file in the team's mockup project" to:

    Team: use the Penpot team pinned for this repo. The dispatch prompt injects team_id and team_name above — look for Penpot team: <name> (<id>). Call mcp__penpot__list_files with that team_id first to reuse an existing file; fall back to mcp__penpot__create_file with that team_id if none matches.

  • If the prompt does NOT carry a Penpot team: line (legacy dispatch from an older service), the skill falls back to list_teams + picks the first. Logs a one-line "no team pinned — using fallback" warning in the handoff comment.

Operator docs

  • CLAUDE.md §"Penpot MCP auth" gains a block on the per-repo team config, with a runbook for adding a new repo:
    1. Create the team in Penpot UI.
    2. Invite the designer Penpot user to it (owner/admin, the designer needs both to create files + export frames).
    3. Look up the team UUID (Penpot URL: .../#/settings/members?team-id=<uuid>).
    4. Add the team_by_repo.<repo> entry to config/agents.json.
    5. Restart the service.

Verification

  • Unit test in webhook-config.test.ts — loader accepts the new block, rejects malformed UUIDs.
  • Unit test in agent-runner.test.ts — task envelope for a penpot_mcp: true agent includes the effective team id/name when the repo is mapped.
  • Manual: configure charles/claude-hookspeon-manager, label-dispatch a new mockup ticket, confirm the resulting Penpot file lands in the right team (visible in the handoff deep-link URL path).

Out of scope

  • Automating Penpot team creation from the service (operator task).
  • Per-issue team override (one repo → one team is enough today).
  • Moving existing design.jacquin.app-wide files between teams — we live with the Default-team mockups already accumulated.

References

  • apps/server/src/agent-runner.ts::buildMcpSetup — where the Penpot MCP gets registered; the env var forwarding happens here.
  • apps/server/src/main.ts::setPenpotMcpConfig — startup token load, hardcodes the base URL.
  • skills/design-implement.md step 2 — the current "find team" prompt.
  • 2026-04-21 incident: designer task ce585c20 stuck on mcp__penpot__list_teams with a valid response it couldn't turn into action.
## User story As an operator running the designer fleet across multiple repos, I want each watched repo to **pin its Penpot team** in config, so that the designer never has to guess which team to work in — and so that `charles/claude-hooks` → `peon-manager`, `charles/proxmox-iac` → some-other-team, etc., stays explicit and auditable rather than inferred from `list_teams`. Today `skills/design-implement.md` step 2 just says "find-or-create the Penpot file in the team's mockup project", and the agent calls `mcp__penpot__list_teams` to pick one. On 2026-04-21, task `ce585c20` stopped at that step with 10 turns and $0.54 spent — the agent read a transit-json-encoded team list (`~u41e41004-…`) and never proceeded to create anything. Pinning the team in config removes the guess entirely. ## Acceptance criteria ### Config schema - [ ] `config/agents.json` grows a top-level `penpot` block: ```json { "penpot": { "base_url": "https://design.jacquin.app", "default_team_id": "41e41004-fcd0-8115-8007-cd4ef7a479dd", "team_by_repo": { "charles/claude-hooks": { "team_name": "peon-manager", "team_id": "<uuid>" }, "charles/proxmox-iac": { "team_name": "<other>", "team_id": "<uuid>" } } } } ``` - [ ] `base_url` replaces the hardcoded `https://design.jacquin.app` in `apps/server/src/main.ts` startup so non-LAN deployments don't have to patch source. - [ ] `default_team_id` is used as the fallback when a repo has no explicit entry (so new repos don't silently break the designer). - [ ] Missing `penpot` block at all = loader proceeds silently with `null`; `penpot_mcp: true` types continue to work but without per-repo pinning (legacy behavior). ### Loader + runner wiring - [ ] `webhook-config.ts` parses + validates the `penpot` block. UUIDs must look like UUIDs; unknown keys under `team_by_repo` log a warning but don't fail the loader. - [ ] `agent-runner.ts` surfaces `penpot_team_id` + `penpot_team_name` in the task prompt envelope when dispatching a `penpot_mcp` agent, resolved from `team_by_repo[task.repo] ?? { team_id: default_team_id }`. - [ ] `/foreman/config` + `/agents` responses include the effective Penpot team for transparency (read-only). ### Skill update - [ ] `skills/design-implement.md` step 2 changes from "find-or-create the Penpot file in the team's mockup project" to: > **Team**: use the Penpot team pinned for this repo. The dispatch prompt injects `team_id` and `team_name` above — look for `Penpot team: <name> (<id>)`. Call `mcp__penpot__list_files` with that `team_id` first to reuse an existing file; fall back to `mcp__penpot__create_file` with that `team_id` if none matches. - [ ] If the prompt does NOT carry a `Penpot team:` line (legacy dispatch from an older service), the skill falls back to `list_teams` + picks the first. Logs a one-line "no team pinned — using fallback" warning in the handoff comment. ### Operator docs - [ ] `CLAUDE.md` §"Penpot MCP auth" gains a block on the per-repo team config, with a runbook for adding a new repo: 1. Create the team in Penpot UI. 2. Invite the `designer` Penpot user to it (owner/admin, the designer needs both to create files + export frames). 3. Look up the team UUID (Penpot URL: `.../#/settings/members?team-id=<uuid>`). 4. Add the `team_by_repo.<repo>` entry to `config/agents.json`. 5. Restart the service. ### Verification - [ ] Unit test in `webhook-config.test.ts` — loader accepts the new block, rejects malformed UUIDs. - [ ] Unit test in `agent-runner.test.ts` — task envelope for a `penpot_mcp: true` agent includes the effective team id/name when the repo is mapped. - [ ] Manual: configure `charles/claude-hooks` → `peon-manager`, label-dispatch a new mockup ticket, confirm the resulting Penpot file lands in the right team (visible in the handoff deep-link URL path). ## Out of scope - Automating Penpot team creation from the service (operator task). - Per-issue team override (one repo → one team is enough today). - Moving existing `design.jacquin.app`-wide files between teams — we live with the Default-team mockups already accumulated. ## References - `apps/server/src/agent-runner.ts::buildMcpSetup` — where the Penpot MCP gets registered; the env var forwarding happens here. - `apps/server/src/main.ts::setPenpotMcpConfig` — startup token load, hardcodes the base URL. - `skills/design-implement.md` step 2 — the current "find team" prompt. - 2026-04-21 incident: designer task `ce585c20` stuck on `mcp__penpot__list_teams` with a valid response it couldn't turn into action.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks#255
No description provided.