- Document service account read-only limitation - Add session 4 log (1Password token update, task tracking) - Track expired token fix in cto-executive-system#8 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
10 KiB
Lessons Learned
Session: 2026-02-18 -- Gitea Agent Skill Implementation
Technical Discoveries
-
Gitea 1.22.6 has no project board REST API
/api/v1/user/projectsreturns 404- The existing SKILL.md had endpoints that do not exist
- Project board API is tracked in Gitea issue #14299, targeted for 1.26+
- Mitigation: Use milestones as project containers -- they have built-in progress tracking
-
Scoped label exclusivity is UI-only in Gitea 1.22.6
- Labels with
exclusive: truedisplay mutual exclusivity in the web UI - The API
POST /repos/{owner}/{repo}/issues/{index}/labelsonly appends -- it does NOT remove conflicting scoped labels - Fix: Use a swap pattern: GET current labels, filter out old scope, add new label ID, PUT full set atomically
- This was the single biggest "gotcha" -- the documentation implies API enforcement that does not exist
- Labels with
-
POST /issues/{index}/assignees does not exist in Gitea 1.22.6
- Returns 404
- Fix: Use
PATCH /repos/{owner}/{repo}/issues/{index}with{"assignees": ["username"]} - This is a general issue edit endpoint, not a dedicated assignee endpoint
-
Issue dependency format is IssueMeta, not simple ID
- Wrong:
{"depends_on_id": 2}-- returns "repository does not exist [id: 0]" - Correct:
{"owner": "terraphim", "repo": "agent-tasks", "index": 2} - Must check the Swagger spec for exact body format rather than guessing
- Wrong:
Debugging Approaches That Worked
-
Test against live API first, then write code
- Verified all 7 open questions against the live Gitea instance before writing any helper functions
- This caught the project board API gap before writing fake endpoints
-
Integration tests with real cleanup
- Each test run creates a timestamped repo (
skill-test-{timestamp}), runs all tests, then deletes it - Trap on EXIT ensures cleanup even on failure
- No mocks -- tests prove the actual API behavior
- Each test run creates a timestamped repo (
-
Incremental testing (run, fail, fix, rerun)
- First test run: 23/26 pass, 3 fail -- immediately identified the label/assignee issues
- Fixed the swap pattern and PATCH approach, second run: 26/26 pass
Pitfalls to Avoid
-
Do not trust Gitea documentation for API behavior -- always verify against the live instance. The swagger spec is the source of truth, but even that can be misleading for edge cases like scoped label exclusivity.
-
Do not assume POST endpoints exist for every resource subpath -- Gitea's API is inconsistent. Some resources (labels) have POST, others (assignees) only work via PATCH on the parent resource.
-
Do not rely on UBS scanner for infrastructure projects -- UBS found no scannable language files (expected for a project with only shell scripts, YAML, and markdown). Pre-commit hooks are more useful here.
-
Pre-commit python version -- The system may not have the Python version specified in
.pre-commit-config.yaml. Changed frompython3.9topython3.12to match the system. Also neededpython3.12-venvapt package for virtualenv support.
Best Practices Discovered
-
Disciplined development phases work well for API integration
- Phase 1 (Research): Discovered the project board API gap before design
- Phase 2 (Design): Created the label swap architecture before implementation
- Phase 3 (Implementation): Tests caught 3 API bugs on first run
-
Default repository pattern -- Using env vars with defaults (
GITEA_OWNER=${GITEA_OWNER:-terraphim}) lets agents work out of the box while remaining configurable. -
JSON output from all helpers -- Every shell function outputs valid JSON, making it trivial for agents to parse results with jq.
-
Idempotent setup scripts --
setup-labels.shchecks for existing resources before creating, making it safe to run repeatedly.
Session: 2026-02-18 -- Articles and Gitea Publishing
Technical Discoveries
-
GitHub Actions billing blocks automated workflows silently
- The
sync-to-gitea.ymlworkflow was failing with: "The job was not started because recent account payments have failed or your spending limit needs to be increased" - This was not visible unless you explicitly checked
gh run view <id>-- the push itself succeeds, only the triggered workflow fails - Workaround: Push directly to Gitea remote using 1Password token:
git push https://oauth2:${GITEA_TOKEN}@git.terraphim.cloud/...
- The
-
Gitea org repos require explicit creation before push
git pushto a non-existent org repo returns "Push to create is not enabled for organizations" (403)- Must create the repo first via API:
POST /api/v1/orgs/{org}/reposwithauto_init: false - Personal repos may allow push-to-create, but org repos do not by default
-
Parallel agent execution works well for article writing
- Launched
technical-ctoandtechnical-writeragents simultaneously - Each produced a distinct article style without interference
- CTO agent: 402 lines, bold narrative voice
- Technical writer agent: 1289 lines, comprehensive reference with appendices
- Both independently verified code snippets against the codebase
- Launched
Debugging Approaches That Worked
-
Check workflow run details, not just push status
git pushsucceeding does not mean the triggered workflow succeeded- Always verify with
gh run list --workflow=<name>after push
-
Direct remote push as fallback for CI failures
- When GitHub Actions is unavailable, adding a gitea remote and pushing with
optoken works immediately - No need to wait for CI billing resolution
- When GitHub Actions is unavailable, adding a gitea remote and pushing with
Pitfalls to Avoid
-
Do not assume GitHub Actions workflows are running -- billing issues cause silent failures. The push succeeds but triggered workflows silently fail. Check
gh run listperiodically. -
Do not push to a Gitea org repo without creating it first -- unlike personal namespaces, organization repos require explicit API creation before accepting pushes.
Best Practices Discovered
-
Dual-agent article writing -- Using two agents with different voices (CTO narrative + technical reference) produces complementary content. The CTO article draws readers in; the technical article provides the implementation detail.
-
Verify all code in articles against live systems -- Both agents independently verified their code snippets against the actual codebase files. This caught zero errors because the source material (SKILL.md, test scripts) was already tested, but the verification step builds confidence.
-
Superseded: Add gitea remote for direct push -- This was the session 2 approach. Session 3 replaced it with Gitea's native pull mirror (see below).
Session: 2026-02-18 -- Replace GitHub Actions with Gitea Pull Mirror
Technical Discoveries
-
Gitea native pull mirror is the right approach
- Gitea can poll GitHub and pull new commits automatically (configurable interval, minimum 10m)
- No GitHub Actions, no tokens in GitHub, no billing dependency
- Set up via
POST /api/v1/repos/migratewith"mirror": true - The Gitea repo becomes read-only; all pushes go to GitHub only
-
Existing repos cannot be converted to pull mirrors
- There is no API to add
mirror: trueto an existing repo - Must delete the repo and recreate via
POST /repos/migrate - This is documented in Gitea's official docs
- There is no API to add
-
The
servicefield in migrate API matters for private reposservice: "git"triesgit clonewhich fails with "terminal prompts disabled" for private reposservice: "github"uses the GitHub API and handles auth correctly- Always use
service: "github"withauth_tokenfor private GitHub repos
-
Failed migrations leave broken empty repos
- If the async clone fails (bad token, network), Gitea creates the repo entry anyway (
mirror: true, empty: true) - The
POST /mirror-syncendpoint returns "Repository is not a mirror" on these broken repos - Must delete and recreate; cannot repair in place
- The migrate API returns empty JSON
{}(null fields) even on success -- check repo status separately
- If the async clone fails (bad token, network), Gitea creates the repo entry anyway (
-
The
github.personal.tokenin 1Password is expiredop://TerraphimPlatform/github.personal.token/tokenreturns a token rejected by GitHub (HTTP 401)gh auth tokenprovides a working OAuth token as alternative- Action item: update the 1Password item with a fresh GitHub PAT
-
Race condition between delete and create on Gitea
DELETE /repos/{owner}/{repo}returns 204 immediately- But the repo name may not be freed yet --
POST /repos/migratereturns 409 - Fix: Always
sleep 10between delete and create, then verify 404 before proceeding
Pitfalls to Avoid
-
Do not use GitHub Actions for Gitea sync -- it adds unnecessary dependency on GitHub billing, requires storing secrets in GitHub, and consumes Actions minutes. Use Gitea's native pull mirror instead.
-
Do not use
service: "git"for private GitHub repos -- it cannot authenticate. Useservice: "github"withauth_token. -
Do not trust the migrate API response body -- it returns null/empty JSON even on success. Always check repo status via
GET /repos/{owner}/{repo}after waiting for the async clone. -
Do not assume deleted repos are immediately gone -- always wait and verify with a GET returning 404 before recreating.
Best Practices Discovered
-
Simplest mirroring wins -- Gitea pull mirror is one API call to set up, zero maintenance, and replaces two GitHub Actions workflows plus a monitoring check.
-
Use
gh auth tokenas GitHub token source -- more reliable than maintaining a separate PAT in 1Password. TheghCLI handles token refresh automatically.
Session: 2026-02-18 -- 1Password Service Account Limitations
Technical Discoveries
- 1Password service accounts are read-only by default
op_zesticai_non_prod.shsources a SERVICE_ACCOUNT (type visible viaop whoami)- Service accounts can
op readbutop item editfails with "Couldn't update the item" - No specific error about permissions -- just a generic failure message
- Write access must be granted explicitly in 1Password admin settings per vault
Pitfalls to Avoid
- Do not assume
op item editworks with service accounts -- test write access before building automation that depends on it. The error message gives no indication that it's a permissions issue.
Best Practices Discovered
- Track infrastructure tasks in a central repo -- Created issue in
AlexMikhalev/cto-executive-systemto track the token update. This prevents losing track of manual steps that couldn't be automated.