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 useadd_column_with_defaultor split the change across multiple migrations. - Adding a
NOT NULLconstraint 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_keyis forbidden — useadd_concurrent_foreign_key.add_indexis forbidden — useadd_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 accessproject.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::Overrideandoverride :methodso 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
argsget rejected; use small IDs and re-fetch.
Gitaly
- Calling
repo.commitsin a loop creates a Gitaly N+1; useGitlab::Git::CommitCollection.fetch_commitsfor batches. - Don't pass user input directly to
Gitalyref names; useGitlab::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_usercorrectly. Auth bugs here create silent data leaks. - Mutations should always return errors as part of the payload, not raise.
Frontend
v-htmlis 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.$deletefor reactivity. New Vue 3 components should not. - GraphQL queries must declare a
@clientdirective 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
_htmlsuffix andsafe_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_beshares the record across examples but not across files. Don't mutate it, or uselet_it_be(refind: true).:freeze_timedoesn't freeze Sidekiq; useSidekiq::Testing.inline!and freeze separately.- Time zones:
Time.nowis forbidden — useTime.zone.now. TheGitlab::ChangeTimezonecop 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.
Related
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.