Flow — Koder Git Forge

  • Area: Developer Platform
  • Path: products/dev/flow/platform
  • Kind: Self-hosted git forge, based on Forgejo (community fork of Gitea)
  • Tagline: From idea to deploy.
  • Status: Production — flow.koder.dev · v1.0.19

Role in the stack

Flow is Koder's git forge — the canonical place to host code. Built on Forgejo and customized as Koder Flow, it runs at https://flow.koder.dev and hosts every Koder repository, including the monorepo itself at Koder/koder.

Features

  • Lightweight — runs on almost any machine, from a Raspberry Pi to a small cloud instance
  • Project management — issues, pull requests, wikis, kanban boards
  • Publishing — releases for software downloads, package registry (Docker, npm, and more), granular public access for private repos (public releases, binaries-only, public packages)
  • Organizations + team permissions, CI integration, code search, LDAP, OAuth
  • Privacy-first — built with privacy in mind from the ground up
  • Mobile app — native AndroidiOS client in `app` for on-the-go access
  • kdb-next integration — shadow-write/read to TiKV cluster (Phase 7.1, tenant 1000019)
  • Audit logging — structured audit log via modules/kall for admin actions
  • Migration manifest — RFC-005: migrations/v{TAG}.toml at tag commit surfaces upgrade constraints in release UI and API
  • Periodic backups (RFC-004)[backups] scheduler + worker pool ship encrypted git mirrors to Google Drive and Microsoft OneDrive (FLOW-074 — MultiUploader dispatches by dest.Provider); Phase 4 added adaptive backoff on DriveGraph throttle signals (FLOW-073), exponential-backoff retry on transient failures (FLOW-075), admin CSV export of the policyrun audit trail (FLOW-077), and guided restore that downloads any successful run's bundle and imports it as a fresh repo (FLOW-076) — RFC-004 Phase 4 fully closed.
  • Async tab counters (#084) — IssuePR tabs fetch open counts via `GET /issuescount + GET pullscount in parallel after page load; zero layout shift, aria-label for screen readers. koder-tab-counters.js + .kf-tab-counter` CSS.
  • StateLabel v2 (#083)shared/state_label_v2.tmpl dual-path template (9 state combos: 5 issue + 4 PR) with semantic CSS classes (kf-state-*). view_title.tmpl migrated. i18n keys in locale_en-US.ini.
  • PageLayout canonical shell (#082, shipped) — Three-phase layout overhaul: (P1) Issue/PR right sidebar sticky + resizable (200–600px, localStorage); (P2) Code view left file-tree sidebar — compact sibling-file listing with active-file highlight, width persistent (kf_code_sidebar_w), toggle persists via sessionStorage; (P3) Mobile fullscreen drawer — position: fixed; transform: translateX(-100%) slide-in + animated backdrop, isMobileDrawer() JS branch. kf-code-layout / kf-code-sidebar / kf-sidebar-backdrop CSS classes; kf-page-layout.js handles both left (code) and right (issue) sidebars with direction-aware drag. Follow-up: #092 (issue list sidebar + Playwright regression suite).
  • kf-* Playwright regression suite gated in CI (#181) — the four kf-*.test.e2e.ts specs (page-layout right sidebar persistence, codeissue sidebar layout, StateLabel v2 dual-path badge, async tab counters — 20 assertions) now gate on every push via `.giteaworkflowsflow-e2e.yml. runs-on: linux (the sanctioned ci-runner, infra-RFC-002 pool), internal sparse checkout, make backend + make 'test-e2e-sqlite#kf-' filtered to **chromium headless** (PLAYWRIGHT_PROJECT`PLAYWRIGHT_FLAGS=chromium) — no GUIXvfbDocker, runs as the non-root ci user. Inaugural run (22765, commit 65633f7424) green. Build recipe corrects two fork traps: make build no-ops (a build/ dir shadows the phony target → use make backend), and bindata needs an un-chained make generate (→ build no-bindata, serve on-disk public/assets). The chromium runtime libs are one-time runner provisioning, documented in infra/ci/runbooks/runner-add.kmd §4b. Closes the headless-first.kmd §R9 coverage gate for Flow's shipped UI surface.
  • MCP server full surface (#094, #095, #096) — RFC-001 Phases 4–5 + RFC-003 Phase 2 — HTTP+SSE transport mounted at /api/v1/mcp/{stream,message} with bearer-token auth + per-session JSON-RPC dispatch (30min idle TTL). resources/list enumerates repos://, meta://, credentials://; the credentials reader walks the repo→org→account hierarchy with per-scope ability gates and returns resolved_from provenance + audit-logged decrypt via creds.Reveal. tools/list + tools/call ship the four write tools (create_issue, comment_issue, create_pr, request_review) with JSON-Schema inputs, scope-mismatch ↔ MCP error mapping, and isError-wrapped tool failures so the LLM can read and react without aborting. In-process tests cover SSE handshake, URI parsing, dispatch error paths, and validation gates.
  • Credential hierarchy + abilities (#098) — RFC-003 Phase 3services/credentials.FetchAuthorized wraps the existing walk with an AbilitiesChecker injection point; MembershipAbilities{} is the default (admin sees all; accountowner-only; orgmember; repo=member). Scopes the requester cannot see are silently skipped (cross-tenant returns 404, not 403, per specs/multi-tenancy/contract.kmd). Provenance.ResolvedFrom() returns the canonical type/id string for API/MCP responses.
  • OpenTelemetry HTTP + manual spans (#099)modules/tracing wraps the main HTTP mux with otelhttp (no-op when [tracing].enabled=false); package-scoped tracers in modules/kdb and services/mcp/handler.go emit spans on every shadow op and JSON-RPC dispatch with stable attribute keys (kdb.op, mcp.method). Health-check paths are skipped via WithFilter. Settings live in [tracing] (enabled / servicename / otlpendpoint / otlpinsecure / excludepaths). OTLP exporter wiring is deferred to #103.
  • Rebrand configs Phase 2 (#100) — RFC-005 — Buildcontrib sweep aligns the deployed binary name: Makefile EXECUTABLE = koder-flow (removes the transitional ln -f shim, fixes a stale main.Koder FlowVersion ldflag with an invalid space), .air.toml reloads koder-flow, IDEautocompletionlegal samples updated, systemd-unit socket-activation comments now reference koder-flow.main.socket. Out-of-scope items (`modelsgiteamigrations package path, gitea* Prometheus metric names, GITEAROOT/GITEACONF` env vars, Dockerfile WORKDIR) tracked in #102.
  • Built-in meta-lint Action (#097) — RFC-002 Phase 3koder-flow/meta-lint@v1 registered in modules/actions built-in registry (rewrites uses: → in-process run: koder-flow actions meta-lint). services/meta.LintFilesystem walks meta/ and surfaces structural findings (missingmalformed frontmatter, invalid status, intra-layer duplicates of (type, name); cross-layer = valid override). CLI emits GitHub-Actions-compatible ::error file=X,line=Y::msg annotations and exits 1 when findings exist. Template workflow shipped at `optionsworkflows/meta-lint.yml`.
  • OTLP gRPC exporter wiring (#103)modules/tracing.Init() instantiates sdktrace.NewTracerProvider with otlptracegrpc client when [tracing].enabled=true && [tracing].otlp_endpoint != ""; resource attrs carry service.name=koder-flow, service.version=<AppVer>, deployment.environment=<RunMode>. Sampling via [tracing].sample_ratio (default 1.0, with AlwaysSampleNeverSample shortcuts at the extremes). `cmdweb.go graceful-shutdowns the provider after graceful.Done()` so in-flight spans flush to the collector. Otelsdk + exportersotlpotlptraceotlptracegrpc promoted to direct deps.
  • Dockerfile WORKDIR rebrand (#102 sub-1) — RFC-005 Phase 3 begin/app/gitea//app/koder-flow/ in Dockerfile + Dockerfile.rootless; xx-verify koder-flow (regression fix — the Forgejo-era xx-verify gitea would not find the binary after #100's EXECUTABLE = koder-flow flip); inverted backward-compat symlink (ln -s koder-flow gitea so legacy scripts still work); Makefile reproduce-build consumes new path. Env vars + path mounts + Prometheus metric names + Go package paths remain in sub-tickets #104–#107.
  • koder-flow-fmt subcommand (#102 sub-5 / #107)build/code-batch-process.go ships a koder-flow-fmt subcommand for make fmt; the legacy gitea-fmt invocation continues to work via dual-case (case "koder-flow-fmt", "gitea-fmt":) for one release. Bonus: corrected a stale koder.dev/dev/flow/engine/build/codeformat import path (post-RFC-003 location).
  • Package paths rename (#102 sub-2 / #104)models/gitea_migrations/models/koder_flow_migrations/ (git mv preserves history). Package declaration updated in the 3 root files; 58 files swept via sed for both prefix styles (koder-flow.org/... legacy and koder.dev/products/dev/flow/engine/... canonical). Symbol references in doctorcmdrouters/tests + string literal fixturesDir path + configs (Makefile, .air.toml, .golangci.yml, .semgrep, coverage-helper, code-batch-process). Migration history is tracked by integer idNumber — no data risk from the Go rename.
  • Env vars rename (#102 sub-3 / #105)GITEA_ROOT/CONF/RUN_MODE/WORK_DIR/CUSTOM/TEMP/APP_INI*KODER_FLOW_* with a one-shot deprecation warning when the legacy name is read. setting.GetenvAliased(canonical, legacy) is the new helper consumed by setting/{setting,path}.go, base/tool.go::SetupGiteaRoot, and tests/test_utils.go::InitTest. Makefile's 30 test-target invocations now pass KODER_FLOW_ROOT/CONF; Dockerfile + Dockerfile.rootless ENV names use KODER_FLOW_* while keeping /var/lib/gitea / /data/gitea as on-disk path values so existing mounted volumes still work.
  • Prometheus metrics dual-emit (#102 sub-4 / #106)modules/metrics/collector.go now uses a dualDesc{canonical, legacy} helper so every one of the 27 metrics is published under BOTH the canonical koder_flow_ prefix and the legacy gitea_ prefix during the rebrand transition. The legacy descriptors carry a DEPRECATED suffix in their # HELP line so scrapers see the migration notice. Grafana dashboards in contrib/flow-monitoring-mixin/ are migrated to query koder_flow_* (config + jsonnet locals + dashboard UID renamed). The gitea_* emission stays for a 2-release deprecation window before removal. RFC-005 Phase 3 umbrella (#102) is now fully shipped — all 5 sub-tickets complete.
  • Rebrand gitea→flow — end-to-end complete (#031 / #210–#213, RFC-005) — a read-only audit of the prod LXC flow (2026-05-30) confirmed the rebrand is complete on the server, not just in-repo. The DB is SQLite and carries 0 tablesindexescolumns named giteaforgejo (of 138 tables — Forgejo uses neutral schema names), so the planned Phase-3 schema migration is a no-op. Data scan found only 144 repository.description rows reading *"Mirror of CrescerX from gitea-crescer"* — accurate provenance to the external Crescer Gitea (s.c.gitea), preserved by design. Filesystemsystemduserbinary already carry the Flow name (`varlib|etc|varlogkoder-flow, koder-flow.service, run-user git, usrlocalbinkoder-flow; no varlibgitea shim remains), so no cutover window or 30-day compat shim is needed. The remaining gitea strings in the tree are deliberate fork-residue: upstream code.gitea.io imports, the servicesmigrationsgitea_* import driver, test fixtures, license headers, and the mandatory .giteaworkflows runner path. Evidence: metacontextrunbooks/rebrand-db-audit.md. Out-of-scope residue (separate): the CI runner's gitea-runner` upstream binary and the Dockerfile's preserved on-disk path values (#105).
  • MCP create_issue labels + assignees (#108) — extends the MCP create_issue tool input schema with labels: string[] and assignees: string[]. Labels resolve through issues_model.GetLabelIDsInRepoByNames (fail loudly when any name doesn't exist on the repo — agentic callers should not silently lose triage data); assignees resolve through user_model.GetUserByName per login. Both inputs dedup on the way in via the package-local dedupStrings. Schema + Go struct shape locked by a regression test in tests/regression/004_mcp_create_issue_labels_assignees_test.go.
  • MCP per-token rate limit (#109) — wires the previously-dormant setting.MCP.MaxCallsPerMinute (default 1000) into a token-bucket per Principal.TokenID in services/mcp/ratelimit.go. Handler.Dispatch consults Allow(tokenID) before any data-plane method; control-plane (initialize, ping, `notifications