Open-Source Wikis

/

GitLab

/

Background

/

Pitfalls

gitlab-org/gitlab

Pitfalls

Areas where new contributors regularly trip. Read these before changing them.

Migrations

  • A naive add_column :foo, :bar, :string, default: 'x' will lock the table on big tables. Always use add_column_with_default or split the change across multiple migrations.
  • Adding a NOT NULL constraint requires a multi-step dance: pre-deploy adds the column nullable, post-deploy backfills, post-deploy adds the constraint via a CHECK + VALIDATE pattern.
  • add_foreign_key is forbidden — use add_concurrent_foreign_key.
  • add_index is forbidden — use add_concurrent_index.
  • Dropping a column needs a multi-deploy plan: stop reading in deploy N, drop in deploy N+1.

N+1 queries

  • The Bullet gem (development) catches obvious N+1, but it doesn't catch GraphQL-driven N+1 in production. Use BatchLoader for resolvers.
  • Project.includes(:namespace) doesn't help if you also access project.creator.email — that's two associations deep.
  • Always test list endpoints with the request-store enabled and the perf bar visible.

Long-lived feature flags

  • Feature flags older than 6 months get warnings.
  • Flags that gate code paths used by 100% of users for months should be removed; the dead else-branch is a maintenance trap.
  • Flags that have never been enabled should be deleted, not preserved "in case".

EE prepend

  • Don't put complex logic inside an EE module that calls super. The EE module should add concerns, not redefine behavior.
  • EE modules must use extend ::Gitlab::Utils::Override and override :method so refactors propagate.
  • prepend_mod_with('Foo') resolves at boot. If the module isn't named correctly, Rails silently doesn't load it.

Sidekiq

  • Workers that read from the DB must declare data_consistency. Otherwise they default to the primary, hammering it.
  • Idempotency requires more than idempotent!. The job body must actually be safe to run twice — usually via a top-of-method state check.
  • Jobs with large args get rejected; use small IDs and re-fetch.

Gitaly

  • Calling repo.commits in a loop creates a Gitaly N+1; use Gitlab::Git::CommitCollection.fetch_commits for batches.
  • Don't pass user input directly to Gitaly ref names; use Gitlab::GitRefValidator.
  • The "Gitaly call deadline exceeded" error often means a slow repo, not a Gitaly bug — bisect with a sample repo.

GraphQL

  • Field-level authorize: runs after BatchLoader resolution. If the field is part of a connection, it runs once per record. Hot paths need scope-level auth.
  • Subscriptions piggyback on Action Cable; the channel must propagate current_user correctly. Auth bugs here create silent data leaks.
  • Mutations should always return errors as part of the payload, not raise.

Frontend

  • v-html is forbidden by ESLint. If you really need to render HTML from the server, use Banzai.
  • Custom buttons / modals violate the GitLab UI design system. Always use components from @gitlab/ui.
  • Vue 2 components rely on this.$set / this.$delete for reactivity. New Vue 3 components should not.
  • GraphQL queries must declare a @client directive when reading from local cache; mixing local and remote in one query causes Apollo to refetch unexpectedly.

i18n

  • _('foo bar') extracts a translation. Don't interpolate Ruby into the string before extraction:

    # Wrong
    _("Hello, #{name}")
    # Right
    format(_('Hello, %{name}'), name: name)
  • HTML in translations needs _html suffix and safe_format.

Banzai

  • New reference filters often forget to check user permissions before exposing the referenced object's title.
  • Reference cache invalidation is per-target. If your filter resolves a foreign object, invalidate that target's cache when the foreign changes.

Tests

  • let_it_be shares the record across examples but not across files. Don't mutate it, or use let_it_be(refind: true).
  • :freeze_time doesn't freeze Sidekiq; use Sidekiq::Testing.inline! and freeze separately.
  • Time zones: Time.now is forbidden — use Time.zone.now. The Gitlab::ChangeTimezone cop catches violations.

Auto-deletion

  • A project marked for deletion in EE can take up to a configurable delay before actual destroy. Code that assumes "destroyed = gone" will see stale projects.
  • User deletion reassigns owned content to a "ghost" user. Code that joins to author/assignee must handle the ghost case.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Pitfalls – GitLab wiki | Factory