Bogdan Denkovych activity https://gitlab.com/bdenkovych 2026-03-17T20:52:57Z tag:gitlab.com,2026-03-17:5214706050 Bogdan Denkovych opened incident #2737: GDK Troubleshooting Report 94ced8b6-a9e6-4947-9a7b-a43dc44e6eb3: `gdk install` does not set up test databases at GitL... 2026-03-17T20:52:57Z bdenkovych Bogdan Denkovych [email protected]

After new gdk instance install, following "Simple installation" guide, and running any spec, like bin/rspec ee/spec/models/ee/group_spec.rb:965, I see the following issue:

~/gdk/gitlab$ bin/rspec ee/spec/models/ee/group_spec.rb:965
Creating a backup of secrets file /home/bogdanvlviv/gdk/gitlab/config/secrets.yml at /home/bogdanvlviv/gdk/gitlab/tmp/tests/backups/secrets.yml.orig.1749764350

An error occurred while loading ./ee/spec/models/ee/group_spec.rb.
Failure/Error: super(connection, underlying_table(connection, table_name))

ActiveRecord::StatementInvalid:
  PG::UndefinedTable: ERROR:  relation "application_settings" does not exist
  LINE 10:  WHERE a.attrelid = '"application_settings"'::regclass
                               ^
# ./lib/gitlab/database/schema_cache_with_renamed_table.rb:25:in `columns'
# ./lib/gitlab/database/schema_cache_with_renamed_table.rb:29:in `columns_hash'
# ./app/models/concerns/cacheable_attributes.rb:31:in `build_from_defaults'
# ./lib/gitlab/application_setting_fetcher.rb:52:in `in_memory_application_settings'
# ./lib/gitlab/application_setting_fetcher.rb:25:in `cached_application_settings'
# ./lib/gitlab/application_setting_fetcher.rb:11:in `current_application_settings'
# ./lib/gitlab/current_settings.rb:15:in `block in current_application_settings'
# ./gems/gitlab-safe_request_store/lib/gitlab/safe_request_store/null_store.rb:37:in `fetch'
# ./lib/gitlab/current_settings.rb:15:in `current_application_settings'
# ./lib/banzai/filter/asset_proxy_filter.rb:67:in `initialize_settings'
# ./config/initializers/asset_proxy_settings.rb:7:in `block in <main>'
# ./config/initializers/asset_proxy_settings.rb:6:in `<main>'
# ./config/environment.rb:7:in `<top (required)>'
# ./spec/spec_helper.rb:27:in `require_relative'
# ./spec/spec_helper.rb:27:in `<top (required)>'
# ./ee/spec/models/ee/group_spec.rb:3:in `<top (required)>'
# ------------------
# --- Caused by: ---
# PG::UndefinedTable:
#   ERROR:  relation "application_settings" does not exist
#   LINE 10:  WHERE a.attrelid = '"application_settings"'::regclass
#                                ^
#   ./lib/gitlab/database/schema_cache_with_renamed_table.rb:25:in `columns'
Run options: include {:locations=>{"./ee/spec/models/ee/group_spec.rb"=>[965]}}

All examples were filtered out

Finished in 0.00004 seconds (files took 5.09 seconds to load)
0 examples, 0 failures, 1 error occurred outside of examples

To resolve that issue I had to run RAILS_ENV=test bin/rails db:setup. I didn't need to do that manually in the past.

Steps to replicate (optional)

Environment

General

  • Operating System: Linux 6.11.0-26-generic #26~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Apr 17 19:20:47 UTC 2 x86_64 x86_64 GNU/Linux
  • Architecture: x86_64
  • Ruby Version: ruby 3.3.8 (2025-04-09 revision b200bad6cd) [x86_64-linux]
  • GDK version: aac934e0
  • Package Manager: mise-en-place 2025.6.1 linux-x64 (2025-06-09)
Variables
PATH=$HOME/.local/share/mise/installs/node/20.12.2/bin:$HOME/.local/share/mise/installs/yarn/1.22.19/bin:$HOME/.local/share/mise/installs/redis/7.0.14/bin:$HOME/.local/share/mise/installs/minio/2022-07-15T03-44-22Z/bin:$HOME/.local/share/mise/installs/postgres/16.8/bin:$HOME/.local/share/mise/installs/postgres/14.9/bin:$HOME/.cargo/bin:$HOME/.local/share/mise/installs/ruby/3.3.8/bin:$HOME/.local/share/mise/installs/ruby/3.2.4/bin:$HOME/.local/share/mise/installs/shellcheck/0.10.0:$HOME/.local/share/mise/installs/markdownlint-cli2/0.18.1/bin:$HOME/.local/share/mise/installs/vale/3.11.2:$HOME/.local/share/mise/installs/go/1.24.4/bin:$HOME/.local/share/mise/installs/python/3.13.3/bin:$HOME/.local/share/mise/installs/sqlite/3.49.2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/snap/bin:$HOME/.fzf/bin
LANG=en_US.UTF-8
LANGUAGE=
LC_ALL=en_US.UTF-8
LDFLAGS=
CPPFLAGS=
PKG_CONFIG_PATH=
LIBPCREDIR=
RUBY_CONFIGURE_OPTS=
GDK Configuration
---
asdf:
  opt_out: true
gdk:
  protected_config_files:
  - gitlab/config/gitlab.yml
gitlab:
  rails:
    puma:
      threads_max: 1
      threads_min: 0
      workers: 1
hostname: gdk.test
https:
  enabled: true
mise:
  enabled: true
nginx:
  enabled: true
  ssl:
    certificate: gdk.test.pem
    key: gdk.test-key.pem
omniauth:
  group_saml:
    enabled: true
openldap:
  enabled: true
port: 3443

GDK Doctor

Result
[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[33mW[0m[32m.[0m[32m.[0m[32m.[0m[33mW[0m[33mW[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[33mW[0m[32m.[0m[32m.[0m
⚠️  [33mWARNING[0m: Your GDK may need attention.

GDK Configuration
================================================================================
Please review the following diff(s) and/or consider running `gdk reconfigure`:

gitlab/config/gitlab.yml
--------------------------------------------------------------------------------
[36m@@ -899,19 +899,6 @@[m [mdevelopment:[m
   <<: *base[m
   omniauth:[m
[31m-    allow_single_sign_on: true[m
[31m-    auto_link_saml_user: false[m
[31m-    block_auto_created_users: false[m
     providers:[m
     - { name: 'group_saml' }[m
[31m-    - { name: 'saml',[m
[31m-        label: 'okta',[m
[31m-        groups_attribute: 'groups',[m
[31m-        args: {[m
[31m-                assertion_consumer_service_url: 'https://gdk.test:3443/users/auth/saml/callback',[m
[31m-                idp_cert_fingerprint: '47:AA:5B:E5:34:CA:64:10:1C:3B:BF:CF:A7:F1:9F:38:B8:A0:2F:3E:E4:01:1D:66:38:09:33:16:2E:38:56:B6',[m
[31m-                idp_sso_target_url: 'https://trial-2663056.okta.com/app/trial-2663056_gdksamlsso_1/exks29bppguI4aztw697/sso/saml',[m
[31m-                issuer: 'https://gdk.test:3443',[m
[31m-                name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'[m
[31m-              } }[m
 [m
 [m



[Correctable] Git Maintenance Recommendation
================================================================================
We recommend enabling git-maintenance to avoid slowdowns of local git operations like fetch, pull, and checkout.

To enable it, run `git maintenance start` in each repository:

git -C $HOME/gdk maintenance start
git -C $HOME/gdk/gitlab maintenance start


Stale Data
================================================================================
You might encounter a PG::CheckViolation error during database migrations, likely due to stale data in the ci database that belongs in the main database, or vice versa. To address this, you can run:

  gdk truncate-legacy-tables


Telemetry
================================================================================
As GitLab team member, we kindly ask you to enable telemetry, which reports command durations and crashes back to the GDK maintainers, so we can improve GDK for all contributors.

To enable telemetry, run:

  gdk telemetry

To opt out of this suggestion, run:

  touch .cache/.no-telemetry-diagnostic


ℹ️  You may autocorrect 1 problem by running `gdk doctor --correct` or `gdk doctor -C`

Ruby Environment

Gem Environment
RubyGems Environment:
  - RUBYGEMS VERSION: 3.6.9
  - RUBY VERSION: 3.3.8 (2025-04-09 patchlevel 144) [x86_64-linux]
  - INSTALLATION DIRECTORY: $HOME/.local/share/mise/installs/ruby/3.3.8/lib/ruby/gems/3.3.0
  - USER INSTALLATION DIRECTORY: $HOME/.local/share/gem/ruby/3.3.0
  - CREDENTIALS FILE: $HOME/.local/share/gem/credentials
  - RUBY EXECUTABLE: $HOME/.local/share/mise/installs/ruby/3.3.8/bin/ruby
  - GIT EXECUTABLE: /usr/bin/git
  - EXECUTABLE DIRECTORY: $HOME/.local/share/mise/installs/ruby/3.3.8/bin
  - SPEC CACHE DIRECTORY: $HOME/.cache/gem/specs
  - SYSTEM CONFIGURATION DIRECTORY: $HOME/.local/share/mise/installs/ruby/3.3.8/etc
  - RUBYGEMS PLATFORMS:
     - ruby
     - x86_64-linux
  - GEM PATHS:
     - $HOME/.local/share/mise/installs/ruby/3.3.8/lib/ruby/gems/3.3.0
     - $HOME/.local/share/gem/ruby/3.3.0
  - GEM CONFIGURATION:
     - :update_sources => true
     - :verbose => true
     - :backtrace => true
     - :bulk_threshold => 1000
  - REMOTE SOURCES:
     - https://rubygems.org/
  - SHELL PATH:
     - $HOME/.local/share/mise/installs/node/20.12.2/bin
     - $HOME/.local/share/mise/installs/yarn/1.22.19/bin
     - $HOME/.local/share/mise/installs/redis/7.0.14/bin
     - $HOME/.local/share/mise/installs/minio/2022-07-15T03-44-22Z/bin
     - $HOME/.local/share/mise/installs/postgres/16.8/bin
     - $HOME/.local/share/mise/installs/postgres/14.9/bin
     - $HOME/.cargo/bin
     - $HOME/.local/share/mise/installs/ruby/3.3.8/bin
     - $HOME/.local/share/mise/installs/ruby/3.2.4/bin
     - $HOME/.local/share/mise/installs/shellcheck/0.10.0
     - $HOME/.local/share/mise/installs/markdownlint-cli2/0.18.1/bin
     - $HOME/.local/share/mise/installs/vale/3.11.2
     - $HOME/.local/share/mise/installs/go/1.24.4/bin
     - $HOME/.local/share/mise/installs/python/3.13.3/bin
     - $HOME/.local/share/mise/installs/sqlite/3.49.2/bin
     - /usr/local/sbin
     - /usr/local/bin
     - /usr/sbin
     - /usr/bin
     - /sbin
     - /bin
     - /usr/games
     - /usr/local/games
     - /snap/bin
     - /snap/bin
     - $HOME/.fzf/bin
Bundle Environment

Environment

Bundler       2.6.5
  Platforms   ruby, x86_64-linux
Ruby          3.3.8p144 (2025-04-09 revision b200bad6cd40d08e9f33b93e1a85c270b337867c) [x86_64-linux]
  Full Path   $HOME/.local/share/mise/installs/ruby/3.3.8/bin/ruby
  Config Dir  $HOME/.local/share/mise/installs/ruby/3.3.8/etc
RubyGems      3.6.9
  Gem Home    $HOME/.local/share/mise/installs/ruby/3.3.8/lib/ruby/gems/3.3.0
  Gem Path    $HOME/.local/share/gem/ruby/3.3.0:$HOME/.local/share/mise/installs/ruby/3.3.8/lib/ruby/gems/3.3.0
  User Home   $HOME
  User Path   $HOME/.local/share/gem/ruby/3.3.0
  Bin Dir     $HOME/.local/share/mise/installs/ruby/3.3.8/bin
Tools         
  Git         2.49.0
  RVM         not installed
  rbenv       not installed
  chruby      not installed

Bundler Build Metadata

Built At          2025-02-20
Git SHA           cffd973142d
Released Version  true

Gemfile

Gemfile

# frozen_string_literal: true

source 'https://rubygems.org'

gemspec path: 'gem/'

group :development do
  gem 'lefthook', '~> 1.10.10', require: false
  gem 'rubocop', require: false
  gem "rubocop-rake", "~> 0.6.0", require: false
  gem 'yard', '~> 0.9.37', require: false
  gem 'pry-byebug' # See doc/howto/pry.md
end

group :test do
  gem 'gitlab-styles', '~> 13.0.2', require: false
  gem 'irb', '~> 1.15.1', require: false
  gem 'rspec', '~> 3.13.0', require: false
  gem 'rspec_junit_formatter', '~> 0.6.0', require: false
  gem 'simplecov-cobertura', '~> 2.1.0', require: false
  gem 'webmock', '~> 3.25', require: false
end

group :development, :test, :danger do
  gem 'gitlab-dangerfiles', '~> 4.8.1', require: false
  gem 'resolv', '~> 0.6.0', require: false

  gem 'ruby-lsp', "~> 0.23.0", require: false
  gem 'ruby-lsp-rspec', "~> 0.1.10", require: false
end

Gemfile.lock

PATH
  remote: gem
  specs:
    gitlab-development-kit (0.2.19)
      gitlab-sdk (~> 0.3.1)
      rake (~> 13.1)
      sentry-ruby (~> 5.23)
      tty-markdown (~> 0.7.2)
      tty-spinner (~> 0.9.3)
      zeitwerk (~> 2.6.15)

GEM
  remote: https://rubygems.org/
  specs:
    activesupport (7.1.3.4)
      base64
      bigdecimal
      concurrent-ruby (~> 1.0, >= 1.0.2)
      connection_pool (>= 2.2.5)
      drb
      i18n (>= 1.6, < 2)
      minitest (>= 5.1)
      mutex_m
      tzinfo (~> 2.0)
    addressable (2.8.7)
      public_suffix (>= 2.0.2, < 7.0)
    ast (2.4.2)
    base64 (0.2.0)
    bigdecimal (3.1.8)
    byebug (11.1.3)
    claide (1.1.0)
    claide-plugins (0.9.2)
      cork
      nap
      open4 (~> 1.3)
    coderay (1.1.3)
    colored2 (3.1.2)
    concurrent-ruby (1.3.3)
    connection_pool (2.4.1)
    cork (0.3.0)
      colored2 (~> 3.1)
    crack (1.0.0)
      bigdecimal
      rexml
    csv (3.3.0)
    danger (9.4.3)
      claide (~> 1.0)
      claide-plugins (>= 0.9.2)
      colored2 (~> 3.1)
      cork (~> 0.1)
      faraday (>= 0.9.0, < 3.0)
      faraday-http-cache (~> 2.0)
      git (~> 1.13)
      kramdown (~> 2.3)
      kramdown-parser-gfm (~> 1.0)
      no_proxy_fix
      octokit (>= 4.0)
      terminal-table (>= 1, < 4)
    danger-gitlab (8.0.0)
      danger
      gitlab (~> 4.2, >= 4.2.0)
    diff-lcs (1.5.1)
    docile (1.4.0)
    drb (2.2.1)
    faraday (2.9.2)
      faraday-net_http (>= 2.0, < 3.2)
    faraday-http-cache (2.5.1)
      faraday (>= 0.8)
    faraday-net_http (3.1.0)
      net-http
    git (1.19.1)
      addressable (~> 2.8)
      rchardet (~> 1.8)
    gitlab (4.20.1)
      httparty (~> 0.20)
      terminal-table (>= 1.5.1)
    gitlab-dangerfiles (4.8.1)
      danger (>= 9.3.0)
      danger-gitlab (>= 8.0.0)
      rake (~> 13.0)
    gitlab-sdk (0.3.1)
      activesupport (>= 5.2.0)
      rake (~> 13.0)
      snowplow-tracker (~> 0.8.0)
    gitlab-styles (13.0.2)
      rubocop (~> 1.68.0)
      rubocop-capybara (~> 2.21.0)
      rubocop-factory_bot (~> 2.26.1)
      rubocop-graphql (~> 1.5.4)
      rubocop-performance (~> 1.21.1)
      rubocop-rails (~> 2.26.0)
      rubocop-rspec (~> 3.0.4)
      rubocop-rspec_rails (~> 2.30.0)
    hashdiff (1.1.1)
    httparty (0.22.0)
      csv
      mini_mime (>= 1.0.0)
      multi_xml (>= 0.5.2)
    i18n (1.14.5)
      concurrent-ruby (~> 1.0)
    io-console (0.7.2)
    irb (1.15.1)
      pp (>= 0.6.0)
      rdoc (>= 4.0.0)
      reline (>= 0.4.2)
    json (2.7.2)
    kramdown (2.4.0)
      rexml
    kramdown-parser-gfm (1.1.0)
      kramdown (~> 2.0)
    language_server-protocol (3.17.0.3)
    lefthook (1.10.10)
    logger (1.6.6)
    method_source (1.0.0)
    mini_mime (1.1.5)
    minitest (5.24.1)
    multi_xml (0.7.1)
      bigdecimal (~> 3.1)
    mutex_m (0.2.0)
    nap (1.1.0)
    net-http (0.4.1)
      uri
    no_proxy_fix (0.1.2)
    octokit (6.1.1)
      faraday (>= 1, < 3)
      sawyer (~> 0.9)
    open4 (1.3.4)
    parallel (1.25.1)
    parser (3.3.3.0)
      ast (~> 2.4.1)
      racc
    pastel (0.8.0)
      tty-color (~> 0.5)
    pp (0.6.2)
      prettyprint
    prettyprint (0.2.0)
    prism (1.3.0)
    pry (0.14.2)
      coderay (~> 1.1)
      method_source (~> 1.0)
    pry-byebug (3.10.1)
      byebug (~> 11.0)
      pry (>= 0.13, < 0.15)
    psych (5.1.2)
      stringio
    public_suffix (5.1.1)
    racc (1.8.0)
    rack (3.1.8)
    rainbow (3.1.1)
    rake (13.2.1)
    rbs (3.8.1)
      logger
    rchardet (1.8.0)
    rdoc (6.6.3.1)
      psych (>= 4.0.0)
    regexp_parser (2.9.2)
    reline (0.5.9)
      io-console (~> 0.5)
    resolv (0.6.0)
    rexml (3.3.1)
      strscan
    rouge (4.4.0)
    rspec (3.13.0)
      rspec-core (~> 3.13.0)
      rspec-expectations (~> 3.13.0)
      rspec-mocks (~> 3.13.0)
    rspec-core (3.13.0)
      rspec-support (~> 3.13.0)
    rspec-expectations (3.13.0)
      diff-lcs (>= 1.2.0, < 2.0)
      rspec-support (~> 3.13.0)
    rspec-mocks (3.13.0)
      diff-lcs (>= 1.2.0, < 2.0)
      rspec-support (~> 3.13.0)
    rspec-support (3.13.0)
    rspec_junit_formatter (0.6.0)
      rspec-core (>= 2, < 4, != 2.12.0)
    rubocop (1.68.0)
      json (~> 2.3)
      language_server-protocol (>= 3.17.0)
      parallel (~> 1.10)
      parser (>= 3.3.0.2)
      rainbow (>= 2.2.2, < 4.0)
      regexp_parser (>= 2.4, < 3.0)
      rubocop-ast (>= 1.32.2, < 2.0)
      ruby-progressbar (~> 1.7)
      unicode-display_width (>= 2.4.0, < 3.0)
    rubocop-ast (1.32.3)
      parser (>= 3.3.1.0)
    rubocop-capybara (2.21.0)
      rubocop (~> 1.41)
    rubocop-factory_bot (2.26.1)
      rubocop (~> 1.61)
    rubocop-graphql (1.5.4)
      rubocop (>= 1.50, < 2)
    rubocop-performance (1.21.1)
      rubocop (>= 1.48.1, < 2.0)
      rubocop-ast (>= 1.31.1, < 2.0)
    rubocop-rails (2.26.2)
      activesupport (>= 4.2.0)
      rack (>= 1.1)
      rubocop (>= 1.52.0, < 2.0)
      rubocop-ast (>= 1.31.1, < 2.0)
    rubocop-rake (0.6.0)
      rubocop (~> 1.0)
    rubocop-rspec (3.0.5)
      rubocop (~> 1.61)
    rubocop-rspec_rails (2.30.0)
      rubocop (~> 1.61)
      rubocop-rspec (~> 3, >= 3.0.1)
    ruby-lsp (0.23.11)
      language_server-protocol (~> 3.17.0)
      prism (>= 1.2, < 2.0)
      rbs (>= 3, < 4)
      sorbet-runtime (>= 0.5.10782)
    ruby-lsp-rspec (0.1.22)
      ruby-lsp (~> 0.23.0)
    ruby-progressbar (1.13.0)
    sawyer (0.9.2)
      addressable (>= 2.3.5)
      faraday (>= 0.17.3, < 3)
    sentry-ruby (5.23.0)
      bigdecimal
      concurrent-ruby (~> 1.0, >= 1.0.2)
    simplecov (0.21.2)
      docile (~> 1.1)
      simplecov-html (~> 0.11)
      simplecov_json_formatter (~> 0.1)
    simplecov-cobertura (2.1.0)
      rexml
      simplecov (~> 0.19)
    simplecov-html (0.12.3)
    simplecov_json_formatter (0.1.4)
    snowplow-tracker (0.8.0)
    sorbet-runtime (0.5.11911)
    stringio (3.1.1)
    strings (0.2.1)
      strings-ansi (~> 0.2)
      unicode-display_width (>= 1.5, < 3.0)
      unicode_utils (~> 1.4)
    strings-ansi (0.2.0)
    strscan (3.1.0)
    terminal-table (3.0.2)
      unicode-display_width (>= 1.1.1, < 3)
    tty-color (0.6.0)
    tty-cursor (0.7.1)
    tty-markdown (0.7.2)
      kramdown (>= 1.16.2, < 3.0)
      pastel (~> 0.8)
      rouge (>= 3.14, < 5.0)
      strings (~> 0.2.0)
      tty-color (~> 0.5)
      tty-screen (~> 0.8)
    tty-screen (0.8.2)
    tty-spinner (0.9.3)
      tty-cursor (~> 0.7)
    tzinfo (2.0.6)
      concurrent-ruby (~> 1.0)
    unicode-display_width (2.5.0)
    unicode_utils (1.4.0)
    uri (0.13.0)
    webmock (3.25.0)
      addressable (>= 2.8.0)
      crack (>= 0.3.2)
      hashdiff (>= 0.4.0, < 2.0.0)
    yard (0.9.37)
    zeitwerk (2.6.15)

PLATFORMS
  ruby

DEPENDENCIES
  gitlab-dangerfiles (~> 4.8.1)
  gitlab-development-kit!
  gitlab-styles (~> 13.0.2)
  irb (~> 1.15.1)
  lefthook (~> 1.10.10)
  pry-byebug
  resolv (~> 0.6.0)
  rspec (~> 3.13.0)
  rspec_junit_formatter (~> 0.6.0)
  rubocop
  rubocop-rake (~> 0.6.0)
  ruby-lsp (~> 0.23.0)
  ruby-lsp-rspec (~> 0.1.10)
  simplecov-cobertura (~> 2.1.0)
  webmock (~> 3.25)
  yard (~> 0.9.37)

BUNDLED WITH
   2.6.5

Gemspecs

gitlab-development-kit.gemspec

# frozen_string_literal: true

$LOAD_PATH.unshift(File.expand_path('lib', __dir__))
require 'gitlab_development_kit'

Gem::Specification.new do |spec|
  spec.name          = 'gitlab-development-kit'
  spec.version       = GDK::GEM_VERSION
  spec.authors       = ['Jacob Vosmaer', 'GitLab']
  spec.email         = ['[email protected]']

  spec.summary       = 'CLI for GitLab Development Kit'
  spec.description   = 'CLI for GitLab Development Kit.'
  spec.homepage      = 'https://gitlab.com/gitlab-org/gitlab-development-kit'
  spec.license       = 'MIT'
  spec.files         = ['lib/gitlab_development_kit.rb']
  spec.executables   = ['gdk']

  spec.required_ruby_version = '>= 3.2.0'
  spec.metadata['rubygems_mfa_required'] = 'true'

  spec.add_dependency 'gitlab-sdk', '~> 0.3.1'
  spec.add_dependency 'rake', '~> 13.1'
  spec.add_dependency 'sentry-ruby', '~> 5.23'
  spec.add_dependency 'tty-markdown', '~> 0.7.2'
  spec.add_dependency 'tty-spinner', '~> 0.9.3'
  spec.add_dependency 'zeitwerk', '~> 2.6.15'
end

Telemetry

Network information
COMMAND     PID        USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
slapd     87537 $USER    7u  IPv4 209293      0t0  TCP krb5.gdk.test:3890 (LISTEN)
gitlab-wo 87621 $USER    6u  IPv4 204708      0t0  TCP krb5.gdk.test:3333 (LISTEN)
gitlab-wo 87621 $USER    7u  IPv4 204709      0t0  TCP krb5.gdk.test:40879 (LISTEN)
nginx     87625 $USER    5u  IPv4 206417      0t0  TCP krb5.gdk.test:http-alt (LISTEN)
gitlab-ss 87626 $USER    6u  IPv4 184649      0t0  TCP krb5.gdk.test:2222 (LISTEN)
nginx     87646 $USER    5u  IPv4 206417      0t0  TCP krb5.gdk.test:http-alt (LISTEN)
topology- 88002 $USER    3u  IPv6 204724      0t0  TCP *:9095 (LISTEN)
topology- 88002 $USER    6u  IPv6 204726      0t0  TCP *:9096 (LISTEN)
webpack   88043 $USER   18u  IPv4 209310      0t0  TCP krb5.gdk.test:3808 (LISTEN)
node      88070 $USER   28u  IPv4 208199      0t0  TCP krb5.gdk.test:39551 (LISTEN)
node      88070 $USER   35u  IPv4 212360      0t0  TCP krb5.gdk.test:45511 (LISTEN)
workerd   88337 $USER   12u  IPv4 212345      0t0  TCP krb5.gdk.test:46651 (LISTEN)
workerd   88337 $USER   13u  IPv4 212347      0t0  TCP krb5.gdk.test:9229 (LISTEN)
workerd   88337 $USER   14u  IPv4 212349      0t0  TCP krb5.gdk.test:3443 (LISTEN)
workerd   88402 $USER   14u  IPv4  25749      0t0  TCP krb5.gdk.test:34781 (LISTEN)
workerd   88402 $USER   16u  IPv4 204755      0t0  TCP krb5.gdk.test:41983 (LISTEN)
workerd   88402 $USER   17u  IPv4 204757      0t0  TCP krb5.gdk.test:37175 (LISTEN)

Logs

Git Repositories

Git Status and HEAD for Gdk

Git Status:

On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

Git HEAD:

commit aac934e0242ef46ac53f74cf3e16e5c1e326312e
Git Status and HEAD for Gitaly

Git Status:

HEAD detached at 55f4fb94e
nothing to commit, working tree clean

Git HEAD:

commit 55f4fb94e39f60e1d4c76deac5d6f20c6202c68f
Git Status and HEAD for Gitlab

Git Status:

On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

Git HEAD:

commit ac0a217c721258e11c0aa11fe22a48a46d854d6c

Created at: 13/06/2025 00:44:14 EEST

tag:gitlab.com,2026-03-17:5212950950 Bogdan Denkovych deleted project branch 20184-gpat-on-credentials at GitLab.org / GitLab 2026-03-17T13:36:47Z bdenkovych Bogdan Denkovych [email protected]

Bogdan Denkovych (debf7240) at 17 Mar 13:36

tag:gitlab.com,2026-03-17:5212947422 Bogdan Denkovych pushed to project branch master at GitLab.org / GitLab 2026-03-17T13:36:03Z bdenkovych Bogdan Denkovych [email protected]

Bogdan Denkovych (3fbbe9ea) at 17 Mar 13:36

Merge branch '20184-gpat-on-credentials' into 'master'

... and 1 more commit

tag:gitlab.com,2026-03-17:5212944279 Bogdan Denkovych accepted merge request !222002: Show granular personal access tokens on credentials page at GitLab.org / GitLab 2026-03-17T13:35:23Z bdenkovych Bogdan Denkovych [email protected]

What does this MR do and why?

  • Show granular personal access tokens on Credentials page
  • If the personal access token is granular, then show permissions and access for each granular scope

Screenshots or screen recordings

Screenshot_2026-02-04_at_12.31.25_pm

How to set up and validate locally

  1. Enable feature-flag in rails console:
Feature.enable(:granular_personal_access_tokens)
  1. Log-in as admin.
  2. Create a granular personal access token by navigating to Preferences > Personal access tokens.
  3. Then, navigate to the Admin area and navigate to Credentials to see the token.
  4. Disable the feature-flag in rails console and refresh the page:
Feature.disable(:granular_personal_access_tokens)
  1. You should see the token but with an Inactive warning message.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Related to gitlab-org#20184

tag:gitlab.com,2026-03-17:5212704246 Bogdan Denkovych approved merge request !222785: Add Email OTP enrollment rake tasks at GitLab.org / GitLab 2026-03-17T12:47:38Z bdenkovych Bogdan Denkovych [email protected]

What does this MR do and why?

Implements three Rake tasks for managing Email OTP enrollment, in support of mandatory Email OTP enforcement on GitLab.com and, for those that want it, eventually on self-managed instances. Closes #589173.

Tasks added

gitlab:email_otp:enrol — Sets email_otp_required_after in bulk for all in-scope users. Defaults to DRY_RUN=true so it can be used safely to count affected users before committing.

Supports three operational modes:

Mode Parameters Effect
Enrol new users ENROL_AT=<date> Sets enrollment date for all in-scope users not yet enrolled
Shift a cohort ENROL_AT=<new_date> EXISTING_ENROL_AT=<old_date> Moves a specific cohort to a new date
Revert a cohort EXISTING_ENROL_AT=<old_date> Clears enrollment for a specific cohort (sets to NULL)

The latter two in particular can be used to rollback / revert an enrolment.

gitlab:email_otp:enforce — Enables require_minimum_email_based_otp_for_users_with_passwords instance setting.

gitlab:email_otp:unenforce — Disables the same setting.

In-scope user definition

A user is in scope for new enrollment when all of the following are true:

  • users.state = 'active' and users.user_type = 0 (human, non-bot)
  • users.password_automatically_set = false (self-set password)
  • users.otp_required_for_login = false (no TOTP)
  • No WebAuthn registrations with authentication_mode = 2 (second-factor)
  • user_details.email_otp_required_after IS NULL (not already enrolled)

Expected operator workflow

# 1. Dry-run to see how many users are in scope
bundle exec rake gitlab:email_otp:enrol ENROL_AT=2027-01-01T00:00:00Z

# 2. Enrol for real
bundle exec rake gitlab:email_otp:enrol DRY_RUN=false ENROL_AT=2027-01-01T00:00:00Z

# 3. Confirm count is now 0
bundle exec rake gitlab:email_otp:enrol ENROL_AT=2027-01-01T00:00:00Z

# 4. Enable enforcement
bundle exec rake gitlab:email_otp:enforce

For rollback or date-shifting a cohort:

# Shift cohort
bundle exec rake gitlab:email_otp:enrol DRY_RUN=false ENROL_AT=2027-02-01T00:00:00Z EXISTING_ENROL_AT=2027-01-01T00:00:00Z

# Revert cohort to unenrolled
bundle exec rake gitlab:email_otp:enrol DRY_RUN=false EXISTING_ENROL_AT=2027-01-01T00:00:00Z

Example run

My GDK has a tiny number of rows, but to give a sense of the script output:

Click to expand
% ENROL_AT="2027-01-01T01:02:03Z" DRY_RUN=true bundle exec rake gitlab:email_otp:enrol
Dry Run: Yes
Setting enrollment date: 2027-01-01 01:02:03 UTC
Applying to existing enrollment date (target cohort):
Batch Size: 1000
Sleep between batches: 0s
Feature Flag (:email_based_mfa) enabled: Yes
Is this correct? yes
[2026-02-24T03:42:01Z][DRY RUN] Enrolling users...
[2026-02-24T03:42:01Z][DRY RUN] Batch 1: 20 rows updated (cumulative: 20)
[2026-02-24T03:42:01Z][DRY RUN] ✓ Enrollment complete. Total users enrolled: 20
% ENROL_AT="2027-01-01T01:02:03Z" DRY_RUN=false bundle exec rake gitlab:email_otp:enrol
Dry Run: No
Setting enrollment date: 2027-01-01 01:02:03 UTC
Applying to existing enrollment date (target cohort):
Batch Size: 1000
Sleep between batches: 0.1s
Feature Flag (:email_based_mfa) enabled: Yes
Is this correct? yes
[2026-02-24T03:42:31Z] Enrolling users...
[2026-02-24T03:42:31Z] Batch 1: 20 rows updated (cumulative: 20)
[2026-02-24T03:42:31Z] ✓ Enrollment complete. Total users enrolled: 20
[1] pry(main)> UserDetail.where(email_otp_required_after: '2027-01-01T01:02:03Z').count
  UserDetail Count (1.3ms)  SELECT COUNT(*) FROM "user_details" WHERE "user_details"."email_otp_required_after" = '2027-01-01 01:02:03' 
=> 20
% EXISTING_ENROL_AT="2027-01-01T01:02:03Z" DRY_RUN=false bundle exec rake gitlab:email_otp:enrol
Dry Run: No
Setting enrollment date:
Applying to existing enrollment date (target cohort): 2027-01-01 01:02:03 UTC
Batch Size: 1000
Sleep between batches: 0.1s
Feature Flag (:email_based_mfa) enabled: Yes
Is this correct? yes
[2026-02-24T03:43:07Z] Enrolling users...
[2026-02-24T03:43:07Z] Batch 1: 20 rows updated (cumulative: 20)
[2026-02-24T03:43:07Z] ✓ Enrollment complete. Total users enrolled: 20
[2] pry(main)> UserDetail.where(email_otp_required_after: '2027-01-01T01:02:03Z').count
  UserDetail Count (0.6ms)  SELECT COUNT(*) FROM "user_details" WHERE "user_details"."email_otp_required_after" = '2027-01-01 01:02:03' 
=> 0

Database performance strategy

each_batch is the GitLab way of creating batches, and scans over windows as follows (4427 is a random user ID):

SELECT "users"."id" FROM "users" WHERE "users"."state" = 'active' AND "users"."user_type" IN (0, 6, 4, 13) AND "users"."user_type" = 0 AND "users"."id" >= 4427 ORDER BY "users"."id" ASC LIMIT 1 OFFSET 1000

The performance of that query is straightforward and can be seen at https://console.postgres.ai/gitlab/gitlab-production-main/sessions/49251/commands/146968

A materialised view is used to operate over each_batch:

Enrolling unenrolled users

WITH batch AS MATERIALIZED (SELECT "users"."id", "users"."password_automatically_set", "users"."otp_required_for_login" FROM "users" WHERE "users"."state" = 'active' AND "users"."user_type" IN (0, 6, 4, 13) AND "users"."user_type" = 0 AND "users"."id" >= REDACTED AND "users"."id" < REDACTED LIMIT 1000),
filtered_batch AS MATERIALIZED (
  SELECT id FROM batch
  WHERE password_automatically_set IS NOT TRUE
    AND otp_required_for_login IS NOT TRUE
  LIMIT 1000
),
without_webauthn AS MATERIALIZED (
  SELECT filtered_batch.id FROM filtered_batch
  LEFT JOIN webauthn_registrations ON webauthn_registrations.user_id = filtered_batch.id
    AND webauthn_registrations.authentication_mode = 2
  WHERE webauthn_registrations.user_id IS NULL
  LIMIT 1000
)
UPDATE user_details SET email_otp_required_after = '2027-01-02 03:04:05'
WHERE user_id IN (SELECT id FROM without_webauthn) AND email_otp_required_after IS NULL

https://console.postgres.ai/gitlab/gitlab-production-main/sessions/49564/commands/147734

Note that PostgresAI says "Query returned 0 rows. This may not reflect production performance or use the same query plan. If you expect results, try adjusting parameters (e.g., different ID values)." However I believe the "Trigger ... calls=XXXX" at the end indicates the number of records that would be updated.

Shifting Enrollment dates

WITH batch AS MATERIALIZED (
  SELECT "users"."id", "users"."password_automatically_set", "users"."otp_required_for_login" 
  FROM "users" 
  WHERE "users"."state" = 'active' 
    AND "users"."user_type" IN (0, 6, 4, 13) 
    AND "users"."user_type" = 0 
    AND "users"."id" >= REDACTED 
    AND "users"."id" < REDACTED
  LIMIT 1000)
UPDATE user_details SET email_otp_required_after = '2026-04-06 20:01:02'
WHERE user_id IN (SELECT id FROM batch) AND email_otp_required_after = '2026-04-06 20:00:00'

https://console.postgres.ai/gitlab/gitlab-production-main/sessions/49564/commands/147733

References

This task is somewhat similar to the token expiry management task in that it's authentication & date related, and iterates many records using each_batch.

TODO

  • Run against a production clone to increase confidence in correctness, and get a sense of runtime (noting that clones are slow)

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

  • "You have considered the technical impacts on GitLab.com, Dedicated and self-managed." — Task is designed for GitLab.com operator use but is usable on dedicated or self-managed; dry-run default and interactive confirmation reduce risk for all contexts.
  • "You have considered that existing data may be surprisingly varied." — checks are in place to avoid touching unintended rows, dry-run gives confidence.
  • "You have considered the performance, reliability, and availability impacts of this change on large customers who may have significantly more data than the average customer." — this is made with .com in mind. Plus BATCH_SLEEP default is tunable, script is safely interruptible and re-runnable, replication lag guidance linked in code comments.
  • "You have added information for database reviewers in the MR description." Yep
  • "You have included enough instrumentation to facilitate debugging and proactive performance improvements through observability." I think so - there is logging throughout
  • "You have added/updated documentation or decided that documentation changes are unnecessary for this MR."
  • "You have confirmed that if this MR contains changes to processing or storing of credentials or tokens, authorization, and authentication methods, or other items described in the security review guidelines, you have added the security label and you have @-mentioned @gitlab-com/gl-security/appsec."
tag:gitlab.com,2026-03-17:5212292392 Bogdan Denkovych approved merge request !225580: Protected Terraform States: Add database migration and model [PART 1] at GitLab.org / GitLab 2026-03-17T11:13:28Z bdenkovych Bogdan Denkovych [email protected]

What does this MR do and why?

Create the data layer for Terraform state protection rules, enabling project maintainers to define rules that restrict who can write to a Terraform state based on project role and request source (PAT, CI job, CI on protected branch).

This is the foundational MR (1 of 7) for the protected Terraform states feature. It creates the terraform_state_protection_rules database table and the Terraform::StateProtectionRule model following the packages_protection_rules pattern.

What's included:

  • Database migration creating terraform_state_protection_rules with:
    • project_id FK (cascade delete)
    • state_name (text, max 255 chars) — exact match against Terraform state name
    • minimum_access_level_for_write (smallint enum: developer, maintainer, owner, admin)
    • allowed_from (smallint enum: anywhere, ci_only, ci_on_protected_branch_only)
    • Unique composite index on (project_id, state_name)
  • Model (Terraform::StateProtectionRule) with enums using Gitlab::Access constants, validations, and for_state_name scope
  • Project association: has_many :terraform_state_protection_rules
  • Factory and full model spec (14 examples)
  • Database dictionary YAML metadata

No behavior changes yet — rules can only be created via Rails console. Enforcement, CRUD services, API endpoints, and UI follow in subsequent MRs.

🛠️ with ❤️ at Siemens

References

Screenshots or screen recordings

Not applicable — backend-only changes.

How to set up and validate locally

  • Run the migration: bundle exec rails db:migrate
  • Open Rails console and create a rule:
    project = Project.first
    rule = project.terraform_state_protection_rules.create!(
      state_name: 'production',
      minimum_access_level_for_write: :maintainer,
      allowed_from: :ci_only
    )
  • Verify uniqueness — creating a second rule with the same project and state name raises ActiveRecord::RecordInvalid
  • Verify cascade delete — deleting the project also deletes associated protection rules
  • Verify the association: project.terraform_state_protection_rules returns the created rules
  • Verify the scope: Terraform::StateProtectionRule.for_state_name('production') returns matching rules

MR acceptance checklist

Please evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

MR Checklist (@gerardo-navarro)
tag:gitlab.com,2026-03-17:5212209676 Bogdan Denkovych approved merge request !222002: Show granular personal access tokens on credentials page at GitLab.org / GitLab 2026-03-17T10:55:15Z bdenkovych Bogdan Denkovych [email protected]

What does this MR do and why?

  • Show granular personal access tokens on Credentials page
  • If the personal access token is granular, then show permissions and access for each granular scope

Screenshots or screen recordings

Screenshot_2026-02-04_at_12.31.25_pm

How to set up and validate locally

  1. Enable feature-flag in rails console:
Feature.enable(:granular_personal_access_tokens)
  1. Log-in as admin.
  2. Create a granular personal access token by navigating to Preferences > Personal access tokens.
  3. Then, navigate to the Admin area and navigate to Credentials to see the token.
  4. Disable the feature-flag in rails console and refresh the page:
Feature.disable(:granular_personal_access_tokens)
  1. You should see the token but with an Inactive warning message.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Related to gitlab-org#20184

tag:gitlab.com,2026-03-12:5198175563 Bogdan Denkovych commented on issue #35251 at GitLab.com / www-gitlab-com 2026-03-12T17:45:51Z bdenkovych Bogdan Denkovych [email protected]

Protected Terraform States: Add database migration and model [PART 1]: gitlab-org/gitlab!225580 (merged)

During review:

Post-review:

@bmarjanovic please add feedback, and compare this review to the average maintainer review.

tag:gitlab.com,2026-03-12:5197900654 Bogdan Denkovych commented on merge request !225580 at GitLab.org / GitLab 2026-03-12T16:33:19Z bdenkovych Bogdan Denkovych [email protected]

@bmarjanovic Could you please provide database maintainer review?

tag:gitlab.com,2026-03-12:5197896223 Bogdan Denkovych commented on merge request !225580 at GitLab.org / GitLab 2026-03-12T16:32:20Z bdenkovych Bogdan Denkovych [email protected]

@gerardo-navarro This MR LGTM from database perspective. I left a few backend suggestions.

tag:gitlab.com,2026-03-12:5197797849 Bogdan Denkovych commented on merge request !225580 at GitLab.org / GitLab 2026-03-12T16:08:33Z bdenkovych Bogdan Denkovych [email protected]

Let's also add spec for this in spec/models/project_spec.rb.

tag:gitlab.com,2026-03-12:5197790476 Bogdan Denkovych commented on merge request !225580 at GitLab.org / GitLab 2026-03-12T16:06:55Z bdenkovych Bogdan Denkovych [email protected]

As per suggestion to remove for_state_name scope.

tag:gitlab.com,2026-03-12:5197790441 Bogdan Denkovych commented on merge request !225580 at GitLab.org / GitLab 2026-03-12T16:06:54Z bdenkovych Bogdan Denkovych [email protected]

Rails scopes always return a collection. Since a project cannot have multiple terraform_state_protection_rules with the same state_name, we will probably rely on Rails find_by_* method, like project.terraform_state_protection_rules.find_by_state_name. Let's remove this scope?

tag:gitlab.com,2026-03-12:5196961531 Bogdan Denkovych commented on issue #592520 at GitLab.org / GitLab 2026-03-12T13:13:58Z bdenkovych Bogdan Denkovych [email protected]

If we wanted to really accurate (and it might be overkill unless the FF stays in the system longer)

I agree.

To correctly disable granular token for authentication by the FF, it should be checked in PersonalAccessToken.find_by_token method for the related user instead.

I think this is important.

tag:gitlab.com,2026-03-12:5196935326 Bogdan Denkovych approved merge request !222002: Show granular personal access tokens on credentials page at GitLab.org / GitLab 2026-03-12T13:08:17Z bdenkovych Bogdan Denkovych [email protected]

What does this MR do and why?

  • Show granular personal access tokens on Credentials page
  • If the personal access token is granular, then show permissions and access for each granular scope

Screenshots or screen recordings

Screenshot_2026-02-04_at_12.31.25_pm

How to set up and validate locally

  1. Enable feature-flag in rails console:
Feature.enable(:granular_personal_access_tokens)
  1. Log-in as admin.
  2. Create a granular personal access token by navigating to Preferences > Personal access tokens.
  3. Then, navigate to the Admin area and navigate to Credentials to see the token.
  4. Disable the feature-flag in rails console and refresh the page:
Feature.disable(:granular_personal_access_tokens)
  1. You should see the token but with an Inactive warning message.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Related to gitlab-org#20184

tag:gitlab.com,2026-03-12:5196934156 Bogdan Denkovych commented on merge request !222002 at GitLab.org / GitLab 2026-03-12T13:08:01Z bdenkovych Bogdan Denkovych [email protected]

Changes in this file may conflict with !227027 (merged). Let's revert all changes in this file for this MR?

tag:gitlab.com,2026-03-12:5196909980 Bogdan Denkovych approved merge request !227027: Revert &quot;Merge branch &#39;589499-show-gpat-as-inactive&#39; into &#39;master&#39;&quot; at GitLab.org / GitLab 2026-03-12T13:02:29Z bdenkovych Bogdan Denkovych [email protected]

What does this MR do and why?

Revert "Merge branch '589499-show-gpat-as-inactive' into 'master'"

This reverts merge request !224006

References

https://gitlab.com/gitlab-org/gitlab/-/work_items/592892

Screenshots or screen recordings

Before After

How to set up and validate locally

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

tag:gitlab.com,2026-03-12:5196652484 Bogdan Denkovych commented on merge request !227027 at GitLab.org / GitLab 2026-03-12T12:03:55Z bdenkovych Bogdan Denkovych [email protected]

We should keep this feature_category to make CI pass.

RSpec.describe API::Entities::PersonalAccessToken, feature_category: :system_access do
tag:gitlab.com,2026-03-11:5192797322 Bogdan Denkovych commented on issue #592520 at GitLab.org / GitLab 2026-03-11T14:25:43Z bdenkovych Bogdan Denkovych [email protected]

@hmehra @alexbuijs See !222002 (comment 3150702867).

I skimmed over the existing granular_personal_access_tokens FF checks. It looks like we incorrectly apply granular_personal_access_tokens FF. We should check granular_personal_access_tokens for current_user only for creating token and for user_settings pages. In other cases, the FF should be checked for personal_access_token.user. Correct?

What concerns me the most is the FF check in https://gitlab.com/gitlab-org/gitlab/-/blob/607bbb317dbf996e515315033ea67170b8065318/app/finders/personal_access_tokens_finder.rb#L81. There is no guarantee that current_user is always present in that context, for instance see https://gitlab.com/gitlab-org/gitlab/-/blob/607bbb317dbf996e515315033ea67170b8065318/lib/gitlab/auth.rb#L301-L304. Meaning that currently the FF could allow users manage granular tokens, but they won't be able to use them/authenticate with them until the FF is globally enabled.

To correctly disable granular token for authentication by the FF, it should be checked in PersonalAccessToken.find_by_token method for the related user instead.