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/kallfor admin actions - Migration manifest — RFC-005:
migrations/v{TAG}.tomlat 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 —MultiUploaderdispatches bydest.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 pullscountin parallel after page load; zero layout shift,aria-labelfor screen readers.koder-tab-counters.js+.kf-tab-counter` CSS. - StateLabel v2 (#083) —
shared/state_label_v2.tmpldual-path template (9 state combos: 5 issue + 4 PR) with semantic CSS classes (kf-state-*).view_title.tmplmigrated. i18n keys inlocale_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 viasessionStorage; (P3) Mobile fullscreen drawer —position: fixed; transform: translateX(-100%)slide-in + animated backdrop,isMobileDrawer()JS branch.kf-code-layout/kf-code-sidebar/kf-sidebar-backdropCSS classes;kf-page-layout.jshandles 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.tsspecs (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 sanctionedci-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-rootciuser. Inaugural run (22765, commit65633f7424) green. Build recipe corrects two fork traps:make buildno-ops (abuild/dir shadows the phony target → usemake backend), and bindata needs an un-chainedmake generate(→ build no-bindata, serve on-diskpublic/assets). The chromium runtime libs are one-time runner provisioning, documented ininfra/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/listenumeratesrepos://,meta://,credentials://; the credentials reader walks the repo→org→account hierarchy with per-scope ability gates and returnsresolved_fromprovenance + audit-logged decrypt viacreds.Reveal.tools/list+tools/callship the four write tools (create_issue,comment_issue,create_pr,request_review) with JSON-Schema inputs, scope-mismatch ↔ MCP error mapping, andisError-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 3 —
services/credentials.FetchAuthorizedwraps the existing walk with anAbilitiesCheckerinjection 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, perspecs/multi-tenancy/contract.kmd).Provenance.ResolvedFrom()returns the canonicaltype/idstring for API/MCP responses. - OpenTelemetry HTTP + manual spans (#099) —
modules/tracingwraps the main HTTP mux withotelhttp(no-op when[tracing].enabled=false); package-scoped tracers inmodules/kdbandservices/mcp/handler.goemit spans on every shadow op and JSON-RPC dispatch with stable attribute keys (kdb.op,mcp.method). Health-check paths are skipped viaWithFilter. 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 transitionalln -fshim, fixes a stalemain.Koder FlowVersionldflag with an invalid space),.air.tomlreloadskoder-flow, IDEautocompletionlegal samples updated, systemd-unit socket-activation comments now referencekoder-flow.main.socket. Out-of-scope items (`modelsgiteamigrationspackage path,gitea*Prometheus metric names,GITEAROOT/GITEACONF` env vars, Dockerfile WORKDIR) tracked in #102. - Built-in meta-lint Action (#097) — RFC-002 Phase 3 —
koder-flow/meta-lint@v1registered inmodules/actionsbuilt-in registry (rewritesuses:→ in-processrun: koder-flow actions meta-lint).services/meta.LintFilesystemwalksmeta/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::msgannotations and exits 1 when findings exist. Template workflow shipped at `optionsworkflows/meta-lint.yml`. - OTLP gRPC exporter wiring (#103) —
modules/tracing.Init()instantiatessdktrace.NewTracerProviderwithotlptracegrpcclient when[tracing].enabled=true && [tracing].otlp_endpoint != ""; resource attrs carryservice.name=koder-flow,service.version=<AppVer>,deployment.environment=<RunMode>. Sampling via[tracing].sample_ratio(default 1.0, withAlwaysSampleNeverSampleshortcuts at the extremes). `cmdweb.gograceful-shutdowns the provider aftergraceful.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-eraxx-verify giteawould not find the binary after #100'sEXECUTABLE = koder-flowflip); inverted backward-compat symlink (ln -s koder-flow giteaso legacy scripts still work);Makefile reproduce-buildconsumes new path. Env vars + path mounts + Prometheus metric names + Go package paths remain in sub-tickets #104–#107. koder-flow-fmtsubcommand (#102 sub-5 / #107) —build/code-batch-process.goships akoder-flow-fmtsubcommand formake fmt; the legacygitea-fmtinvocation continues to work via dual-case (case "koder-flow-fmt", "gitea-fmt":) for one release. Bonus: corrected a stalekoder.dev/dev/flow/engine/build/codeformatimport 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 andkoder.dev/products/dev/flow/engine/...canonical). Symbol references in doctorcmdrouters/tests + string literalfixturesDirpath + configs (Makefile, .air.toml, .golangci.yml, .semgrep, coverage-helper, code-batch-process). Migration history is tracked by integeridNumber— 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 bysetting/{setting,path}.go,base/tool.go::SetupGiteaRoot, andtests/test_utils.go::InitTest. Makefile's 30 test-target invocations now passKODER_FLOW_ROOT/CONF; Dockerfile + Dockerfile.rootless ENV names useKODER_FLOW_*while keeping/var/lib/gitea//data/giteaas on-disk path values so existing mounted volumes still work. - Prometheus metrics dual-emit (#102 sub-4 / #106) —
modules/metrics/collector.gonow uses adualDesc{canonical, legacy}helper so every one of the 27 metrics is published under BOTH the canonicalkoder_flow_prefix and the legacygitea_prefix during the rebrand transition. The legacy descriptors carry aDEPRECATEDsuffix in their# HELPline so scrapers see the migration notice. Grafana dashboards incontrib/flow-monitoring-mixin/are migrated to querykoder_flow_*(config + jsonnet locals + dashboard UID renamed). Thegitea_*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 namedgiteaforgejo(of 138 tables — Forgejo uses neutral schema names), so the planned Phase-3 schema migration is a no-op. Data scan found only 144repository.descriptionrows 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-usergit,usrlocalbinkoder-flow; novarlibgiteashim remains), so no cutover window or 30-day compat shim is needed. The remaininggiteastrings in the tree are deliberate fork-residue: upstreamcode.gitea.ioimports, theservicesmigrationsgitea_*import driver, test fixtures, license headers, and the mandatory.giteaworkflowsrunner path. Evidence:metacontextrunbooks/rebrand-db-audit.md. Out-of-scope residue (separate): the CI runner'sgitea-runner` upstream binary and the Dockerfile's preserved on-disk path values (#105). - MCP create_issue labels + assignees (#108) — extends the MCP
create_issuetool input schema withlabels: string[]andassignees: string[]. Labels resolve throughissues_model.GetLabelIDsInRepoByNames(fail loudly when any name doesn't exist on the repo — agentic callers should not silently lose triage data); assignees resolve throughuser_model.GetUserByNameper login. Both inputs dedup on the way in via the package-localdedupStrings. Schema + Go struct shape locked by a regression test intests/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 perPrincipal.TokenIDinservices/mcp/ratelimit.go.Handler.DispatchconsultsAllow(tokenID)before any data-plane method; control-plane (initialize,ping, `notifications