[{"data":1,"prerenderedAt":814},["ShallowReactive",2],{"/en-us/blog/learning-python-with-a-little-help-from-ai-code-suggestions":3,"navigation-en-us":40,"banner-en-us":450,"footer-en-us":460,"blog-post-authors-en-us-Michael Friedrich":701,"blog-related-posts-en-us-learning-python-with-a-little-help-from-ai-code-suggestions":715,"blog-promotions-en-us":752,"next-steps-en-us":804},{"id":4,"title":5,"authorSlugs":6,"body":8,"categorySlug":9,"config":10,"content":14,"description":8,"extension":27,"isFeatured":12,"meta":28,"navigation":29,"path":30,"publishedDate":20,"seo":31,"stem":35,"tagSlugs":36,"__hash__":39},"blogPosts/en-us/blog/learning-python-with-a-little-help-from-ai-code-suggestions.yml","Learning Python With A Little Help From Ai Code Suggestions",[7],"michael-friedrich",null,"ai-ml",{"slug":11,"featured":12,"template":13},"learning-python-with-a-little-help-from-ai-code-suggestions",false,"BlogPost",{"title":15,"description":16,"authors":17,"heroImage":19,"date":20,"body":21,"category":9,"tags":22},"Learning Python with a little help from AI","Use this guided tutorial, along with GitLab Duo Code Suggestions, to learn a new programming language.",[18],"Michael Friedrich","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749663918/Blog/Hero%20Images/aipower.jpg","2023-11-09","Learning a new programming language can help broaden your software development expertise, open career opportunities, or create fun challenges. However, it can be difficult to decide on one specific approach to learning a new language. Artificial intelligence (AI) can help. In this tutorial, you'll learn how to leverage AI-powered GitLab Duo Code Suggestions for a guided experience in learning the Python programming language with a practical hands-on example.\n\n- [Preparations](#preparations)\n  - [VS Code](#vs-code)\n  - [Code Suggestions](#code-suggestions)\n- [Learning a new programming language: Python](#learning-a-new-programming-language-python)\n    - [Development environment for Python](#development-environment-for-python)\n    - [Hello, World](#hello-world)\n- [Start learning Python with a practical example](#start-learning-python-with-a-practical-example)\n    - [Define variables and print them](#define-variables-and-print-them)\n    - [Explore variable types](#explore-variable-types)\n- [File I/O: Read and print a log file](#file-io-read-and-print-a-log-file)\n- [Flow control](#flow-control)\n    - [Loops and lists to collect files](#loops-and-lists-to-collect-files)\n    - [Conditionally collect files](#conditionally-collect-files)\n- [Functions](#functions)\n    - [Start with a simple log format](#start-with-a-simple-log-format)\n    - [String and data structure operations](#string-and-data-structure-operations)\n    - [Parse log files using regular expressions](#parse-log-files-using-regular-expressions)\n    - [Advanced log format: auth.log](#advanced-log-format-authlog)\n    - [Parsing more types: Structured logging](#parsing-more-types-structured-logging)\n- [Printing results and formatting](#printing-results-and-formatting)\n- [Dependency management and continuous verification](#dependency-management-and-continuous-verification)\n    - [Pip and pyenv: Bringing structure into Python](#pip-and-pyenv-bringing-structure-into-python)\n    - [Automation: Configure CI/CD pipeline for Python](#automation-configure-cicd-pipeline-for-python)\n- [What is next](#what-is-next)\n    - [Async learning exercises](#async-learning-exercises)\n    - [Share your feedback](#share-your-feedback)\n\n## Preparations\n\nChoose your [preferred and supported IDE](https://docs.gitlab.com/user/project/repository/code_suggestions/#enable-code-suggestions-in-other-ides-and-editors), and follow the documentation to enable Code Suggestions for [GitLab.com SaaS](https://docs.gitlab.com/user/project/repository/code_suggestions/#enable-code-suggestions-on-gitlab-saas) or [GitLab self-managed instances](https://docs.gitlab.com/user/project/repository/code_suggestions/#enable-code-suggestions-on-self-managed-gitlab).\n\nProgramming languages can require installing the language interpreter command-line tools or compilers that generate binaries from source code to build and run the application.\n\n**Tip:** You can also use [GitLab Remote Development workspaces](/blog/quick-start-guide-for-gitlab-workspaces/) to create your own cloud development environments, instead of local development environments. This blog post focuses on using VS Code and the GitLab Web IDE.\n\n### VS Code\n\n[Install VS Code](https://code.visualstudio.com/download) on your client, and open it. Navigate to the `Extensions` menu and search for `gitlab workflow`. Install the [GitLab Workflow extension for VS Code](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow). VS Code will also detect the programming languages, and offer to install additional plugins for syntax highlighting and development experience. For example, install the [Python extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python).\n\n### Code Suggestions\n\nFamiliarize yourself with suggestions before actually verifying the suggestions. GitLab Duo Code Suggestions are provided as you type, so you do not need use specific keyboard shortcuts. To accept a code suggestion, press the `tab` key. Also note that writing new code works more reliably than refactoring existing code. AI is non-deterministic, which means that the same suggestion may not be repeated after deleting the code suggestion. While Code Suggestions is in Beta, we are working on improving the accuracy of generated content overall. Please review the [known limitations](https://docs.gitlab.com/user/project/repository/code_suggestions/#known-limitations), as this could affect your learning experience.\n\n**Tip:** The latest release of Code Suggestions supports multiline instructions. You can refine the specifications to your needs to get better suggestions. We will practice this method throughout the blog post.\n\n## Learning a new programming language: Python\n\nNow, let's dig into learning Python, which is one of the [supported languages in Code Suggestions](https://docs.gitlab.com/user/project/repository/code_suggestions/#supported-languages).\n\nBefore diving into the source code, make sure to set up your development environment.\n\n### Development environment for Python\n\n1) Create a new project `learn-python-ai` in GitLab, and clone the project into your development environment. All code snippets are available in this [\"Learn Python with AI\" project](https://gitlab.com/gitlab-da/use-cases/ai/learn-with-ai/learn-python-ai).\n\n```shell\ngit clone https://gitlab.com/NAMESPACE/learn-python-ai.git\n\ncd learn-python-ai\n\ngit status\n```\n\n2) Install Python and the build toolchain. Example on macOS using Homebrew:\n\n```shell\nbrew install python\n```\n\n3) Consider adding a `.gitignore` file for Python, for example this [.gitignore template for Python](https://gitlab.com/gitlab-org/gitlab/-/blob/master/vendor/gitignore/Python.gitignore?ref_type=heads).\n\nYou are all set to learn Python!\n\n### Hello, World\n\nStart your learning journey in the [official documentation](https://www.python.org/about/gettingstarted/), and review the linked resources, for example, the [Python tutorial](https://docs.python.org/3/tutorial/index.html). The [library](https://docs.python.org/3/library/index.html) and [language reference](https://docs.python.org/3/reference/index.html) documentation can be helpful, too.\n\n**Tip:** When I touched base with Python in 2005, I did not have many use cases except as a framework to test Windows 2000 drivers. Later, in 2016, I refreshed my knowledge with the book \"Head First Python, 2nd Edition,\" providing great practical examples for the best learning experience – two weeks later, I could explain the differences between Python 2 and 3. You do not need to worry about Python 2 – it has been deprecated some years ago, and we will focus only on Python 3 in this blog post. In August 2023, \"[Head First Python, 3rd Edition](https://www.oreilly.com/library/view/head-first-python/9781492051282/)\" was published. The book provides a great learning resource, along with the exercises shared in this blog post.\n\nCreate a new file `hello.py` in the root directory of the project and start with a comment saying `# Hello world`. Review and accept the suggestion by pressing the `tab` key and save the file (keyboard shortcut: cmd s).\n\n```markdown\n# Hello world\n```\n\nCommit the change to the Git repository. In VS Code, use the keyboard shortcut `ctrl shift G`, add a commit message, and hit `cmd enter` to submit.\n\nUse the command palette (`cmd shift p`) and search for `create terminal` to open a new terminal. Run the code with the Python interpreter. On macOS, the binary from Homebrew is called `python3`, other operating systems and distributions might use `python` without the version.\n\n```shell\npython3 hello.py\n```\n\n![Hello World, hello GitLab Duo Code Suggestions](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_python_code_sugestions_hello_world.png)\n\n**Tip:** Adding code comments in Python starting with the `#` character before you start writing a function or algorithm will help Code Suggestions with more context to provide better suggestions. In the example above, we did that with `# Hello world`, and will continue doing so in the next exercises.\n\nAdd `hello.py` to Git, commit all changes and push them to your GitLab project.\n\n```shell\ngit add hello.py\n\ngit commit -avm \"Initialize Python\"\n\ngit push\n```\n\nThe source code for all exercises in this blog post is available in this [\"Learn Python with AI\" project](https://gitlab.com/gitlab-da/use-cases/ai/learn-with-ai/learn-python-ai).\n\n## Start learning Python with a practical example\n\nThe learning goal in the following sections involves diving into the language datatypes, variables, flow control, and functions. We will also look into file operations, string parsing, and data structure operations for printing the results. The exercises will help build a command-line application that reads different log formats, works with the data, and provides a summary. This will be the foundation for future projects that fetch logs from REST APIs, and inspire more ideas such as rendering images, creating a web server, or adding Observability metrics.\n\n![Parsing log files into structured objects, example result after following the exercises](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_terminal_parsing_logs_and_pretty_print_results.png)\n\nAs an experienced admin, you can put the script into production and use real-world log format exmples. Parsing and analyzing logs in stressful production incidents can be time-consuming. A local CLI tool is sometimes faster than a log management tool.\n\nLet's get started: Create a new file called `log_reader.py` in the directory root, add it to Git, and create a Git commit.\n\n### Define variables and print them\n\nAs a first step, we need to define the log files location, and the expected file suffix. Therefore, let's create two variables and print them. Actually, ask Code Suggestions to do that for you by writing only the code comments and accepting the suggestions. Sometimes, you need to experiment with suggestions and delete already accepted code blocks. Do not worry – the quality of the suggestions will improve over time as the model generates better suggestions with more context.\n\n![Define log path and file suffix variables](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_log_reader_variables_01.png)\n\n![Print the variables to verify](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_log_reader_variables_02.png)\n\n```python\n# Specify the path and file suffix in variables\npath = '/var/log/'\nfile_suffix = '.log'\n\n# Print the variables\n\nprint(path)\nprint(file_suffix)\n```\n\nNavigate into the VS Code terminal and run the Python script:\n\n```shell\npython3 log_reader.py\n```\n\n![VS Code terminal, printing the variables](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_terminal_print_variables.png)\n\nPython supports many different types in the [standard library](https://docs.python.org/3/library/index.html). Most common types are: Numeric (int, float, complex), Boolean (True, False), and String (str). Data structures include support for lists, tuples, and dictionaries.\n\n### Explore variable types\n\nTo practice different variable types, let's define a limit of log files to read as a variable with the `integer` type.\n\n![Log file variable](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_log_reader_variables_03.png)\n\n```python\n# Define log file limit variable\nlog_file_limit = 1024\n```\n\nCreate a Boolean variable that forces to read all files in the directory, no matter the log file suffix.\n\n```python\n# Define boolean variable whether to read all files recursively\nread_all_files_recursively = True\n```\n\n## File I/O: Read and print a log file\n\nCreate a directory called `log-data` in your project tree. You can copy all file examples from the [log-data directory in the example project](https://gitlab.com/gitlab-da/use-cases/ai/learn-with-ai/learn-python-ai/-/tree/main/log-data?ref_type=heads).\n\nCreate a new file `sample.log` with the following content, or any other two lines that provide a different message at the end.\n\n```text\nOct 17 00:00:04 ebpf-chaos systemd[1]: dpkg-db-backup.service: Deactivated successfully.\nOct 17 00:00:04 ebpf-chaos systemd[1]: Finished Daily dpkg database backup service.\n```\n\nInstruct Code Suggestions to read the file `log-data/sample.log` and print the content.\n\n![Code Suggestions: Read log file and print it](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_read_log_file_and_print.png)\n\n```python\n# Read the file in log-data/sample.log and print its content\nwith open('log-data/sample.log', 'r') as f:\n    print(f.read())\n\n```\n\n**Tip:** You will notice the indent here. The `with open() as f:` statement opens a new scope where `f` is available as stream. This flow requires indenting )`tab`) the code block, and perform actions in this scope, calling `f.read()` to read the file contents, and passing the immediate value as parameter into the `print()` function.\n\nNavigate into the terminal, and run the script again with `python3 log_reader.py`. You will see the file content shown in the VS Code editor, also printed into the terminal.\n\n![VS Code terminal: Read log file, and print it](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_terminal_print_logfile_show_file_sample.png)\n\n## Flow control\n\nReading one log file is not enough – we want to analyze all files in a given directory recursively. For the next exercise, we instruct Code Suggestions to create an index of all files.\n\nPrepare the `log-data` directory with more example files from the [log-data directory in the example project](https://gitlab.com/gitlab-da/use-cases/ai/learn-with-ai/learn-python-ai/-/tree/main/log-data?ref_type=heads). The directory tree should look as follows:\n\n```shell\ntree log-data                                                             ─╯\nlog-data\n├── sample.log\n└── var\n    └── log\n        ├── auth.log\n        ├── syslog.log\n        └── syslog_structured.log\n\n3 directories, 4 files\n```\n\n### Loops and lists to collect files\n\nModify the `path` variable to use the value `log-data/`.\n\n```python\n# Specify the path and file suffix in variables\npath = 'log-data/'\nfile_suffix = '.log'\n```\n\nTell Code Suggestions to read all file paths in the directory into a list. After the collection loop, print the list of file paths.\n\n```python\n# Read all file paths in the directory into a list\n\n# Print the list of log file paths\n```\n\n![Code Suggestion, collect file paths](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_collect_files.png)\n\nAn example of a suggestion could look like this:\n\n```python\n# Read all file paths in the directory into a list\nimport os\n\n# Read all file paths in the directory into a list\nlog_files = []\nfor root, directories, files in os.walk(path):\n    for file in files:\n        if file.endswith(file_suffix):\n            log_files.append(os.path.join(root, file))\n\n# Print the list of log file paths\n\nprint(log_files)\n```\n\nLet's analyze what happens here: The `import os` statement is required to bring the `os` library into the current scope, and we are able to call `os.walk()` later. The `log_files` [list](https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range) is initialized as an empty list using empty brackets. `os.walk()` expects a path as directory tree to start searching for files. The function returns a generator object, which will walk the directory tree recursively. The returned tuples include the collected files, which we want to access in a loop iteraor, using the `for` loop notation. When the file ends with the defined suffix, its full path is appended to the list. This is an optimized and complex example. Do not worry about understanding everything just yet - we will revisit this later.\n\n### Conditionally collect files\n\nModify the instructions and add the Boolean flag evaluation for `read_all_files_recursively` as an input to the glob() function.\n\n```python\n# Read all file paths in the top level directory into a list\n# If read_all_files_recursively is True, read all files recursively\n```\n\n![Code Suggestions, read files recursively conditionally](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_read_files_recursively_conditionally.png)\n\n```text\n# Read all file paths in the top level directory\nimport os\n\n# Read all file paths in the top level directory into a list\n# If read_all_files_recursively is True, read all files recursively\n\nlog_files = []\nfor file in os.listdir(path):\n    if file.endswith(file_suffix):\n        log_files.append(os.path.join(path, file))\n\nif read_all_files_recursively:\n    for root, directories, files in os.walk(path):\n        for file in files:\n            if file.endswith(file_suffix):\n                log_files.append(os.path.join(root, file))\n\n# Print log_files\nprint(log_files)\n```\n\nThe result is not optimal yet because it always executes the first loop, and optionally the second loop. This flow leads to duplicated results when the script is executed.\n\n```shell\npython3 log_reader.py\n\n['log-data/sample.log', 'log-data/sample.log', 'log-data/var/log/auth.log']\n```\n\nExperiment with Code Suggestions instructions to get a solution for the problem. There are different approaches you can take:\n\n1) A potential solution is to wrap the source code into an if-then-else block, and move the `os.listdir()` loop into the else-block.\n\n```python\nif read_all_files_recursively:\n    for root, directories, files in os.walk(path):\n        for file in files:\n            if file.endswith(file_suffix):\n                log_files.append(os.path.join(root, file))\nelse:\n    for file in os.listdir(path):\n        if file.endswith(file_suffix):\n            log_files.append(os.path.join(path, file))\n\n```\n\n2) Alternatively, do not use `append()` to always add a new list entry, but check if the item exists in the list first.\n\n```python\nfor file in os.listdir(path):\n    if file.endswith(file_suffix):\n        # check if the entry exists in the list already\n        if os.path.isfile(os.path.join(path, file)):\n            log_files.append(os.path.join(path, file))\n\nif read_all_files_recursively:\n    for root, directories, files in os.walk(path):\n        for file in files:\n            if file.endswith(file_suffix):\n                # check if the entry exists in the list already\n                if file not in log_files:\n                    log_files.append(os.path.join(root, file))\n\n```\n\n3) Or, we could eliminate duplicate entries after collecting all items. Python allows converting lists into [sets](https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset), which hold unique entries. After applying `set()`, you can again convert the set back into a list. Code Suggestions knows about this possibility, and will help with the comment `# Ensure that only unique file paths are in the list`\n\n![Code Suggestions, converting a list to unique items](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_unique_list.png)\n\n```python\n# Ensure that only unique file paths are in the list\n\nlog_files = list(set(log_files))\n```\n\n4) Take a step back and evaluate whether the variable read_all_files_recursively makes sense. Maybe the default behavior should just be reading all files recursively?\n\n**Tip for testing different paths in VS Code:** Select the code blocks, and press [`cmd /` on macOS](https://code.visualstudio.com/docs/getstarted/keybindings) to comment out the code.\n\n## Functions\n\nLet's create a function called `parse_log_file` that parses a log file, and returns the extracted data. We will define the expected log format and columns to extract, following the [syslog format specification](https://en.wikipedia.org/wiki/Syslog). There are different log format types and also customized formats by developers that need to be taken into account – exercise for later.\n\n### Start with a simple log format\n\nInspect a running Linux VM, or use the following example log file example for additional implementation.\n\n```text\nless /var/log/syslog | grep -v docker\n\nOct 17 00:00:04 ebpf-chaos systemd[1]: Starting Daily dpkg database backup service...\nOct 17 00:00:04 ebpf-chaos systemd[1]: Starting Rotate log files...\nOct 17 00:00:04 ebpf-chaos systemd[1]: dpkg-db-backup.service: Deactivated successfully.\nOct 17 00:00:04 ebpf-chaos systemd[1]: Finished Daily dpkg database backup service.\nOct 17 00:00:04 ebpf-chaos systemd[1]: logrotate.service: Deactivated successfully.\nOct 17 00:00:04 ebpf-chaos systemd[1]: Finished Rotate log files.\nOct 17 00:17:01 ebpf-chaos CRON[727495]: (root) CMD (   cd / && run-parts --report /etc/cron.hourly)\n```\n\nWe can create an algorithm to split each log line by whitespaces, and then join the results again. Let's ask Code Suggestions for help.\n\n```python\n# Split log line \"Oct 17 00:00:04 ebpf-chaos systemd[1]: Finished Rotate log files.\" by whitespaces and save in a list\n\nlog_line = \"Oct 17 00:00:04 ebpf-chaos systemd[1]: Finished Rotate log files.\"\nlog_line_split = log_line.split(\" \")\nprint(log_line_split)\n```\n\nRun the script again to verify the result.\n\n```shell\npython3 log_reader.py\n\n['Oct', '17', '00:00:04', 'ebpf-chaos', 'systemd[1]:', 'Finished', 'Rotate', 'log', 'files.']\n```\n\nThe first three items are part of the datetime string, followed by the host, service, and remaining log message items. Let's practice string operations in Python as the next step.\n\n### String and data structure operations\n\nLet's ask Code Suggestions for help with learning to join strings, and perform list operations.\n\n1. Join the first three items with a whitespace again.\n2. Keep host and service.\n3. Join the remaining variable item count into a string, separated with whitespaces, again.\n4. Store the identified column keys, and their respective values in a new data structure: [dictionary](https://docs.python.org/3/library/stdtypes.html#mapping-types-dict).\n\n![Code suggestions for list items with string operations](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_list_items_string_join_extract.png)\n\n```shell\npython3 log_reader.py\n\n# Array\n['Oct', '17', '00:00:04', 'ebpf-chaos', 'systemd[1]:', 'Finished', 'Rotate', 'log', 'files.']\n\n# Dictionary\n{'datetime': 'Oct 17 00:00:04', 'host': 'ebpf-chaos', 'service': 'systemd[1]:', 'message': ' ebpf-chaos systemd[1]: Finished Rotate log files.'}\n```\n\nA working suggestion can look like the following:\n\n```python\n# Initialize results dictionary with empty values for datetime, host, service, message\n# Loop over log line split\n# Join the first three list items as date string\n# Item 4: host\n# Item 5: service\n# Join the remaining items into a string, separated with whitespaces\n# Print the results after the loop\n\nresults = {'datetime': '', 'host': '', 'service': '', 'message': ''}\n\nfor item in log_line_split:\n\n    if results['datetime'] == '':\n        results['datetime'] = ' '.join(log_line_split[0:3])\n\n    elif results['host'] == '':\n        results['host'] = log_line_split[3]\n\n    elif results['service'] == '':\n        results['service'] = log_line_split[4]\n\n    else:\n        results['message'] += ' ' + item\n\nprint(results)\n```\n\nThe suggested algorithm loops over all log line items, and applies the same operation for the first three items. `log_line_split[0:3]` extracts a slice of three items into a new list. Calling `join()` on a separator character and passing the array as an argument joins the items into a string. The algorithm continues to check for not initialized values for host (Item 4) and service (Item 5)and concludes with the remaining list items appended into the message string. To be honest, I would have used a slightly different algorithm, but it is a great learning curve to see other algorithms, and ways to implement them. Practice with different instructions, and data structures, and continue printing the data sets.\n\n**Tip:** If you need to terminate a script early, you can use `sys.exit()`. The remaining code will not be executed.\n\n```python\nimport sys\nsys.exit(1)\n```\n\nImagine doing these operations for different log formats, and message types – it can get complicated and error-prone very quickly. Maybe there is another approach.\n\n### Parse log files using regular expressions\n\nThere are different syslog format RFCs – [RFC 3164](https://datatracker.ietf.org/doc/html/rfc3164) is obsolete but still found in the wild as default configuration (matching the pattern above), while [RFC 5424](https://datatracker.ietf.org/doc/html/rfc5424) is more modern, including datetime with timezone information. Parsing this format can be tricky, so let's ask Code Suggestions for advice.\n\nIn some cases, the suggestions include regular expressions. They might not match immediately, making the code more complex to debug, with trial and errors. A good standalone resource to text and explain regular expressions is [regex101.com](https://regex101.com/).\n\n**Tip:** You can skip diving deep into regular expressions using the following code snippet as a quick cheat. The next step involves instructing Code Suggestions to use these log patterns, and help us extract all valuable columns.\n\n```python\n# Define the syslog log format regex in a dictionary\n# Add entries for RFC3164, RFC5424\nregex_log_pattern = {\n    'rfc3164': '([A-Z][a-z][a-z]\\s{1,2}\\d{1,2}\\s\\d{2}[:]\\d{2}[:]\\d{2})\\s([\\w][\\w\\d\\.@-]*)\\s(.*)$',\n    'rfc5424': '(?:(\\d{4}[-]\\d{2}[-]\\d{2}[T]\\d{2}[:]\\d{2}[:]\\d{2}(?:\\.\\d{1,6})?(?:[+-]\\d{2}[:]\\d{2}|Z)?)|-)\\s(?:([\\w][\\w\\d\\.@-]*)|-)\\s(.*)$;'\n}\n```\n\nWe know what the function should do, and its input parameters – the file name, and a log pattern to match. The log lines should be split by this regular expression, returning a key-value dictionary for each log line. The function should return a list of dictionaries.\n\n```python\n# Create a function that parses a log file\n# Input parameter: file path\n# Match log line against regex_log_pattern\n# Return the results as dictionary list: log line, pattern, extracted columns\n```\n\n![Code suggestion based on a multiline comment instruction to get a function that parses a log file based on regex patterns](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_log_format_regex_function_instructions_01.png)\n\nRemember the indent for opening a new scope? The same applies for functions in Python. The `def` identifier requires a function name, and a list of parameters, followed by an opening colon. The next lines of code require the indent. VS Code will help with live-linting wrong indent, before the script execution fails, or the CI/CD pipelines.\n\nContinue with Code Suggestions – it might already know that you want to parse all log files, and parse them using the newly created function.\n\n![Code suggestion to parse all log files, and print the result set](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_log_format_regex_function_instructions_02.png)\n\nA full working example can look like this:\n\n```text\nimport os\n\n# Specify the path and file suffix in variables\npath = 'log-data/'\nfile_suffix = '.log'\n\n# Read all file paths in the directory into a list\nlog_files = []\nfor root, directories, files in os.walk(path):\n    for file in files:\n        if file.endswith(file_suffix):\n            log_files.append(os.path.join(root, file))\n\n# Define the syslog log format regex in a dictionary\n# Add entries for RFC3164, RFC5424\nregex_log_pattern = {\n    'rfc3164': '([A-Z][a-z][a-z]\\s{1,2}\\d{1,2}\\s\\d{2}[:]\\d{2}[:]\\d{2})\\s([\\w][\\w\\d\\.@-]*)\\s(.*)$',\n    'rfc5424': '(?:(\\d{4}[-]\\d{2}[-]\\d{2}[T]\\d{2}[:]\\d{2}[:]\\d{2}(?:\\.\\d{1,6})?(?:[+-]\\d{2}[:]\\d{2}|Z)?)|-)\\s(?:([\\w][\\w\\d\\.@-]*)|-)\\s(.*)$;'\n}\n\n# Create a function that parses a log file\n# Input parameter: file path\n# Match log line against regex_log_pattern\n# Return the results as dictionary list: log line, pattern name, extracted columns\nimport re\n\ndef parse_log_file(file_path):\n    # Read the log file\n    with open(file_path, 'r') as f:\n        log_lines = f.readlines()\n\n    # Create a list to store the results\n    results = []\n\n    # Iterate over the log lines\n    for log_line in log_lines:\n        # Match the log line against the regex pattern\n        for pattern_name, pattern in regex_log_pattern.items():\n            match = re.match(pattern, log_line)\n\n            # If the log line matches the pattern, add the results to the list\n            if match:\n                extracted_columns = match.groups()\n                results.append({\n                    'log_line': log_line,\n                    'pattern_name': pattern_name,\n                    'extracted_columns': extracted_columns,\n                    'source_file': file_path\n                })\n\n    # Return the results\n    return results\n\n# Parse all files and print results\nfor log_file in log_files:\n    results = parse_log_file(log_file)\n    print(results)\n\n```\n\nLet's unpack what the `parse_log_file()` function does:\n\n1. Opens the file from `file_path` parameter.\n2. Reads all lines into a new variable `log_lines`.\n3. Creates a results list to store all items.\n4. Iterates over the log lines.\n5. Matches against all regex patterns configured in regex_log_pattern.\n6. If a match is found, extracts the matching column values.\n7. Creates a results item, including the values for the keys `log_line`, `pattern_name`, `extracted_colums`, `source_file`.\n8. Appends the results item to the results list.\n9. Returns the results list.\n\nThere are different variations to this – especially for the returned result data structure. For this specific case, log lines come as list already. Adding a dictionary object instead of a raw log line allows function callers to extract the desired information in the next step. Once a working example has been implemented, you can refactor the code later, too.\n\n### Advanced log format: auth.log\n\nParsing the syslog on a Linux distribution might not unveil the necessary data to analyze. On a virtual machine that exposes port 22 (SSH) to the world, the authentication log is much more interesting – plenty of bots and malicious actors testing default password combinations and often brute force attacks.\n\nThe following snippet from `/var/log/auth.log` on one of my private servers shows the authentication log format and the random attempts from bots using different usernames, etc.\n\n```text\nOct 15 00:00:19 ebpf-chaos sshd[3967944]: Failed password for invalid user ubuntu from 93.254.246.194 port 48840 ssh2\nOct 15 00:00:20 ebpf-chaos sshd[3967916]: Failed password for root from 180.101.88.227 port 44397 ssh2\nOct 15 00:00:21 ebpf-chaos sshd[3967944]: Received disconnect from 93.254.246.194 port 48840:11: Bye Bye [preauth]\nOct 15 00:00:21 ebpf-chaos sshd[3967944]: Disconnected from invalid user ubuntu 93.254.246.194 port 48840 [preauth]\nOct 15 00:00:24 ebpf-chaos sshd[3967916]: Failed password for root from 180.101.88.227 port 44397 ssh2\nOct 15 00:00:25 ebpf-chaos sshd[3967916]: Received disconnect from 180.101.88.227 port 44397:11:  [preauth]\nOct 15 00:00:25 ebpf-chaos sshd[3967916]: Disconnected from authenticating user root 180.101.88.227 port 44397 [preauth]\nOct 15 00:00:25 ebpf-chaos sshd[3967916]: PAM 2 more authentication failures; logname= uid=0 euid=0 tty=ssh ruser= rhost=180.101.88.227 user=root\nOct 15 00:00:25 ebpf-chaos sshd[3967998]: Invalid user teamspeak from 185.218.20.10 port 33436\n```\n\n**Tip for intrusion prevention:** Add a firewall setup, and use [fail2ban](https://en.wikipedia.org/wiki/Fail2ban) to block invalid auth logins.\n\nThe next exercise is to extend the logic to understand the free form log message parts, for example `Failed password for invalid user ubuntu from 93.254.246.194 port 48840 ssh2`. The task is to store the data in an optional dictionary with key value pairs.\n\nCreate a new function that takes the previously parsed log line results as input, and specifically parses the last list item for each line.\n\n1. Count the number of `Failed password` and `Invalid user` messages.\n2. Return the results with count, log file, pattern\n\n![Code suggestions for a log file message parser to count auth.log failures](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_parse_log_message_auth_log.png)\n\nA working suggestion can look like the following code:\n\n```python\n# Create a function that parses a log file message from the last extracted_columns entry\n# Input: Parsed log lines results list\n# Loop over all log lines in the list, and extract the last list item as message\n# Count failure strings in the message: Failed password, Invalid user\n# Return the results if failure count greater 0: log_file, count, failure string\ndef parse_log_file_message(results):\n    failure_results = []\n\n    # Iterate over the log lines\n    for result in results:\n        # Extract the message from the last list item\n        message = result['extracted_columns'][-1]\n\n        # Count the number of failure strings in the message\n        failure_count = message.count('Failed password') + message.count('Invalid user')\n\n        # If the failure count is greater than 0, add the results to the list\n        if failure_count > 0:\n            failure_results.append({\n                'log_file': result['source_file'],\n                'count': failure_count,\n                'failure_string': message\n            })\n\n    # Return the results\n    return failure_results\n\n# Parse all files and print results\nfor log_file in log_files:\n    results = parse_log_file(log_file)\n    failure_results = parse_log_file_message(results)\n    print(failure_results)\n\n```\n\nThe algorithm follows the previous implementations: First, create a results array to store matching data. Then, iterate over the already parsed log_lines in the list. Each log line contains the `extracted_columns` key, which holds the free-form message string at the end. The next step is to call the string object function `count()` to count how many times a given character sequence is contained in a string. The returned numbers are added up to the `failure_count` variable. If it is greater than zero, the result is added to the results list, including the `log_file`, `count` and `failure_string` key-value pairs. After returning the parsed log message results, loop through all log files, parse them, and print the results again.\n\nExecute the script to inspect the detected matches. Note that the data structure can be optimized in future learning steps.\n\n```shell\npython3 log_reader.py\n\n[{'log_file': 'log-data/var/log/auth.log', 'count': 1, 'failure_string': 'sshd[3967944]: Failed password for invalid user ubuntu from 93.254.246.194 port 48840 ssh2'}, {'log_file': 'log-data/var/log/auth.log', 'count': 1, 'failure_string': 'sshd[3967916]: Failed password for root from 180.101.88.227 port 44397 ssh2'}, {'log_file': 'log-data/var/log/auth.log', 'count': 1, 'failure_string': 'sshd[3967916]: Failed password for root from 180.101.88.227 port 44397 ssh2'}, {'log_file': 'log-data/var/log/auth.log', 'count': 1, 'failure_string': 'sshd[3967998]: Invalid user teamspeak from 185.218.20.10 port 33436'}, {'log_file': 'log-data/var/log/auth.log', 'count': 1, 'failure_string': 'sshd[3967998]: Failed password for invalid user teamspeak from 185.218.20.10 port 33436 ssh2'}, {'log_file': 'log-data/var/log/auth.log', 'count': 1, 'failure_string': 'sshd[3968077]: Invalid user mcserver from 218.211.33.146 port 50950'}]\n```\n\n### Parsing more types: Structured logging\n\nApplication developers can use the structured logging format to help machine parsers to extract the key value pairs. Prometheus provides this information in the following structure in syslog:\n\n```text\nOct 17 19:00:10 ebpf-chaos prometheus[594]: ts=2023-10-17T19:00:10.425Z caller=compact.go:519 level=info component=tsdb m\nsg=\"write block\" mint=1697558404661 maxt=1697565600000 ulid=01HCZG4ZX51GTH8H7PVBYDF4N6 duration=148.675854ms\nOct 17 19:00:10 ebpf-chaos prometheus[594]: ts=2023-10-17T19:00:10.464Z caller=head.go:1213 level=info component=tsdb msg\n=\"Head GC completed\" caller=truncateMemory duration=6.845245ms\nOct 17 19:00:10 ebpf-chaos prometheus[594]: ts=2023-10-17T19:00:10.467Z caller=checkpoint.go:100 level=info component=tsd\nb msg=\"Creating checkpoint\" from_segment=2308 to_segment=2309 mint=1697565600000\nOct 17 19:00:10 ebpf-chaos prometheus[594]: ts=2023-10-17T19:00:10.517Z caller=head.go:1185 level=info component=tsdb msg\n=\"WAL checkpoint complete\" first=2308 last=2309 duration=50.052621ms\n```\n\nThis format is easier to parse for scripts, because the message part can be split by whitespaces, and the assignment character `=`. Strings that contain whitespaces are guaranteed to be enclosed with quotes. The downside is that not all programming language libraries provide ready-to-use structured logging libraries, making it harder for developers to adopt this format.\n\nPractice following the previous example to parse the `auth.log` format with additional information. Tell Code Suggestions that you are expecting structured logging format with key-value pairs, and which returned data structure would be great:\n\n```python\n# Create a function that parses a log file message from the last extracted_columns entry\n# Input: Parsed log lines results list\n# Loop over all log lines in the list, and extract the last list item as message\n# Parse structured logging key-value pairs into a dictionary\n# Return results: log_file, dictionary\n```\n\n![Code suggestions for parsing structured logging format in the log file message part](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_propose_structured_logging_message_parser.png)\n\n### Printing results and formatting\n\nMany of the examples used the `print()` statement to print the content on the terminal. Python objects in the standard library support text representation, and for some types it makes more sense (string, numbers), others cannot provide much details (functions, etc.).\n\nYou can also pretty-print almost any data structure (lists, sets, dictionaries) in Python. The JSON library can format data structures in a readable format, and use a given spaces indent to draw the JSON structure on the terminal. Note that we use the `import` statement here to bring libraries into the current scope, and access their methods, for example `json.dumps`.\n\n```python\nimport json\nprint(json.dumps(structured_results, indent=4))\n```\n\n![Parsing log files into structured objects, example result after following the exercises](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_terminal_parsing_logs_and_pretty_print_results.png)\n\nPractice with modifying the existing source code, and replace the code snippets where appropriate. Alternatively, create a new function that implements pretty printing.\n\n```python\n# Create a pretty print function with indent 4\n```\n\n![Code suggestions for pretty-print function](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_pretty_print.png)\n\nThis idea works in a similar fashion with creating your own logger functions...but we have to stop learning and take a break. Before we conclude the first blog post in the learning series, let's ensure that CI/CD and dependencies are set up properly for future exercises and async practice.\n\n## Dependency management and continuous verification\n\n### Pip and pyenv: Bringing structure into Python\n\nDependencies can be managed in the [`requirements.txt` file](https://pip.pypa.io/en/stable/reference/requirements-file-format/), including optional version dependencies. Using `requirements.txt` file also has the advantage of being the single source of truth for local development environments and running continuous builds with GitLab CI/CD. They can use the same installation command:\n\n```shell\npip install -r requirements.txt\n```\n\nSome Linux distributions do not install the pip package manager by default, for example, Ubuntu/Debian require to install the `python3-pip` package.\n\nYou can manage different virtual environments using [venv](https://docs.python.org/3/library/venv.html). This workflow can be beneficial to install Python dependencies into the virtual environment, instead of globally into the OS path which might break on upgrades.\n\n```shell\npip install virtualenv\nvirtualenv venv\nsource venv/bin/activate\n```\n\n### Automation: Configure CI/CD pipeline for Python\n\nThe [CI/CD pipeline](https://docs.gitlab.com/ci/) should continuously lint, test, and build the code. You can mimic the steps from the local development, and add testing more environments and versions:\n\n1. Lint the source code and check for formatting errors. The example uses [Pyflakes](https://pypi.org/project/pyflakes/), a mature linter, and [Ruff](https://docs.astral.sh/ruff/ ), a fast linter written in Rust.\n2. Cache dependencies installed using the pip package manager, following the documentation for [Python caching in GitLab CI/CD](https://docs.gitlab.com/ci/caching/#cache-python-dependencies). This saves time and resources on repeated CI/CD pipeline runs.\n3. Use parallel matrix builds to test different Python versions, based on the available container images on Docker Hub and their tags.\n\n```yaml\nstages:\n  - lint\n  - test\n\ndefault:\n  image: python:latest\n  cache:                      # Pip's cache doesn't store the python packages\n    paths:                    # https://pip.pypa.io/en/stable/topics/caching/\n      - .cache/pip\n  before_script:\n    - python -V               # Print out python version for debugging\n    - pip install virtualenv\n    - virtualenv venv\n    - source venv/bin/activate\n\nvariables:  # Change pip's cache directory to be inside the project directory since we can only cache local items.\n  PIP_CACHE_DIR: \"$CI_PROJECT_DIR/.cache/pip\"\n\n# lint template\n.lint-tmpl:\n  script:\n    - echo \"Linting Python version $VERSION\"\n  parallel:\n    matrix:\n      - VERSION: ['3.9', '3.10', '3.11', '3.12']   # https://hub.docker.com/_/python\n\n# Lint, using Pyflakes: https://pypi.org/project/pyflakes/\nlint-pyflakes:\n  extends: [.lint-tmpl]\n  script:\n    - pip install -r requirements.txt\n    - find . -not -path './venv' -type f -name '*.py' -exec sh -c 'pyflakes {}' \\;\n\n# Lint, using Ruff (Rust): https://docs.astral.sh/ruff/\nlint-ruff:\n  extends: [.lint-tmpl]\n  script:\n    - pip install -r requirements.txt\n    - ruff .\n\n```\n\n![GitLab CI/CD Python lint job view, part of matrix builds](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/gitlab_cicd_python_lint_job_log_view.png)\n\n## What is next\n\nFun fact: GitLab Duo Code Suggestions also helped writing this blog post in VS Code, knowing about the context. In the screenshot, I just wanted to add a tip about [regex101](https://regex101.com/), and GitLab Duo already knew.\n\n![Writing the GitLab blog post in VS Code with support from GitLab Duo Code Suggestions](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/gitlab_duo_code_suggestions_helping_write_the_learning_python_ai_blog_post.png)\n\nIn an upcoming blog, we will look into advanced learning examples with more practical (log) filtering and parallel operations, how to fetch logs from API endpoints (CI/CD job logs for example), and more data analytics and observability. Until then, here are a few recommendations for practicing async.\n\n### Async learning exercises\n\n- Implement the missing `log_file_limit` variable check.\n- Print a summary of the results in Markdown, not only JSON format.\n- Extend the script to accept a search filter as environment variable. Print/count only filtered results.\n- Extend the script to accept a date range. It might require parsing the datetime column in a time object to compare the range.\n- Inspect a GitLab CI/CD pipeline job log, and download the raw format. Extend the log parser to parse this specific format, and print a summary.\n\n### Share your feedback\n\nWhich programming language are you learning or considering learning? Start a new topic on our [community](/community/) forum or Discord and share your experience.\n\nWhen you use [GitLab Duo](/gitlab-duo-agent-platform/) Code Suggestions, please share your thoughts and feedback [in the feedback issue](https://gitlab.com/gitlab-org/gitlab/-/issues/405152).\n",[23,24,25,26],"DevSecOps platform","tutorial","workflow","AI/ML","yml",{},true,"/en-us/blog/learning-python-with-a-little-help-from-ai-code-suggestions",{"title":15,"description":16,"ogTitle":15,"ogDescription":16,"noIndex":12,"ogImage":19,"ogUrl":32,"ogSiteName":33,"ogType":34,"canonicalUrls":32},"https://about.gitlab.com/blog/learning-python-with-a-little-help-from-ai-code-suggestions","https://about.gitlab.com","article","en-us/blog/learning-python-with-a-little-help-from-ai-code-suggestions",[37,24,25,38],"devsecops-platform","aiml","n3ZVbTJhKLM3Cf26XryuC-mYTT38YApAP_SRWaZ2Ye0",{"data":41},{"logo":42,"freeTrial":47,"sales":52,"login":57,"items":62,"search":370,"minimal":401,"duo":420,"switchNav":429,"pricingDeployment":440},{"config":43},{"href":44,"dataGaName":45,"dataGaLocation":46},"/","gitlab logo","header",{"text":48,"config":49},"Get free trial",{"href":50,"dataGaName":51,"dataGaLocation":46},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":53,"config":54},"Talk to sales",{"href":55,"dataGaName":56,"dataGaLocation":46},"/sales/","sales",{"text":58,"config":59},"Sign in",{"href":60,"dataGaName":61,"dataGaLocation":46},"https://gitlab.com/users/sign_in/","sign in",[63,90,185,190,291,351],{"text":64,"config":65,"cards":67},"Platform",{"dataNavLevelOne":66},"platform",[68,74,82],{"title":64,"description":69,"link":70},"The intelligent orchestration platform for DevSecOps",{"text":71,"config":72},"Explore our Platform",{"href":73,"dataGaName":66,"dataGaLocation":46},"/platform/",{"title":75,"description":76,"link":77},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":78,"config":79},"Meet GitLab Duo",{"href":80,"dataGaName":81,"dataGaLocation":46},"/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":83,"description":84,"link":85},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":86,"config":87},"Learn more",{"href":88,"dataGaName":89,"dataGaLocation":46},"/why-gitlab/","why gitlab",{"text":91,"left":29,"config":92,"link":94,"lists":98,"footer":167},"Product",{"dataNavLevelOne":93},"solutions",{"text":95,"config":96},"View all Solutions",{"href":97,"dataGaName":93,"dataGaLocation":46},"/solutions/",[99,123,146],{"title":100,"description":101,"link":102,"items":107},"Automation","CI/CD and automation to accelerate deployment",{"config":103},{"icon":104,"href":105,"dataGaName":106,"dataGaLocation":46},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[108,112,115,119],{"text":109,"config":110},"CI/CD",{"href":111,"dataGaLocation":46,"dataGaName":109},"/solutions/continuous-integration/",{"text":75,"config":113},{"href":80,"dataGaLocation":46,"dataGaName":114},"gitlab duo agent platform - product menu",{"text":116,"config":117},"Source Code Management",{"href":118,"dataGaLocation":46,"dataGaName":116},"/solutions/source-code-management/",{"text":120,"config":121},"Automated Software Delivery",{"href":105,"dataGaLocation":46,"dataGaName":122},"Automated software delivery",{"title":124,"description":125,"link":126,"items":131},"Security","Deliver code faster without compromising security",{"config":127},{"href":128,"dataGaName":129,"dataGaLocation":46,"icon":130},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[132,136,141],{"text":133,"config":134},"Application Security Testing",{"href":128,"dataGaName":135,"dataGaLocation":46},"Application security testing",{"text":137,"config":138},"Software Supply Chain Security",{"href":139,"dataGaLocation":46,"dataGaName":140},"/solutions/supply-chain/","Software supply chain security",{"text":142,"config":143},"Software Compliance",{"href":144,"dataGaName":145,"dataGaLocation":46},"/solutions/software-compliance/","software compliance",{"title":147,"link":148,"items":153},"Measurement",{"config":149},{"icon":150,"href":151,"dataGaName":152,"dataGaLocation":46},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[154,158,162],{"text":155,"config":156},"Visibility & Measurement",{"href":151,"dataGaLocation":46,"dataGaName":157},"Visibility and Measurement",{"text":159,"config":160},"Value Stream Management",{"href":161,"dataGaLocation":46,"dataGaName":159},"/solutions/value-stream-management/",{"text":163,"config":164},"Analytics & Insights",{"href":165,"dataGaLocation":46,"dataGaName":166},"/solutions/analytics-and-insights/","Analytics and insights",{"title":168,"items":169},"GitLab for",[170,175,180],{"text":171,"config":172},"Enterprise",{"href":173,"dataGaLocation":46,"dataGaName":174},"/enterprise/","enterprise",{"text":176,"config":177},"Small Business",{"href":178,"dataGaLocation":46,"dataGaName":179},"/small-business/","small business",{"text":181,"config":182},"Public Sector",{"href":183,"dataGaLocation":46,"dataGaName":184},"/solutions/public-sector/","public sector",{"text":186,"config":187},"Pricing",{"href":188,"dataGaName":189,"dataGaLocation":46,"dataNavLevelOne":189},"/pricing/","pricing",{"text":191,"config":192,"link":194,"lists":198,"feature":278},"Resources",{"dataNavLevelOne":193},"resources",{"text":195,"config":196},"View all resources",{"href":197,"dataGaName":193,"dataGaLocation":46},"/resources/",[199,232,250],{"title":200,"items":201},"Getting started",[202,207,212,217,222,227],{"text":203,"config":204},"Install",{"href":205,"dataGaName":206,"dataGaLocation":46},"/install/","install",{"text":208,"config":209},"Quick start guides",{"href":210,"dataGaName":211,"dataGaLocation":46},"/get-started/","quick setup checklists",{"text":213,"config":214},"Learn",{"href":215,"dataGaLocation":46,"dataGaName":216},"https://university.gitlab.com/","learn",{"text":218,"config":219},"Product documentation",{"href":220,"dataGaName":221,"dataGaLocation":46},"https://docs.gitlab.com/","product documentation",{"text":223,"config":224},"Best practice videos",{"href":225,"dataGaName":226,"dataGaLocation":46},"/getting-started-videos/","best practice videos",{"text":228,"config":229},"Integrations",{"href":230,"dataGaName":231,"dataGaLocation":46},"/integrations/","integrations",{"title":233,"items":234},"Discover",[235,240,245],{"text":236,"config":237},"Customer success stories",{"href":238,"dataGaName":239,"dataGaLocation":46},"/customers/","customer success stories",{"text":241,"config":242},"Blog",{"href":243,"dataGaName":244,"dataGaLocation":46},"/blog/","blog",{"text":246,"config":247},"Remote",{"href":248,"dataGaName":249,"dataGaLocation":46},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":251,"items":252},"Connect",[253,258,263,268,273],{"text":254,"config":255},"GitLab Services",{"href":256,"dataGaName":257,"dataGaLocation":46},"/services/","services",{"text":259,"config":260},"Community",{"href":261,"dataGaName":262,"dataGaLocation":46},"/community/","community",{"text":264,"config":265},"Forum",{"href":266,"dataGaName":267,"dataGaLocation":46},"https://forum.gitlab.com/","forum",{"text":269,"config":270},"Events",{"href":271,"dataGaName":272,"dataGaLocation":46},"/events/","events",{"text":274,"config":275},"Partners",{"href":276,"dataGaName":277,"dataGaLocation":46},"/partners/","partners",{"backgroundColor":279,"textColor":280,"text":281,"image":282,"link":286},"#2f2a6b","#fff","Insights for the future of software development",{"altText":283,"config":284},"the source promo card",{"src":285},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":287,"config":288},"Read the latest",{"href":289,"dataGaName":290,"dataGaLocation":46},"/the-source/","the source",{"text":292,"config":293,"lists":295},"Company",{"dataNavLevelOne":294},"company",[296],{"items":297},[298,303,309,311,316,321,326,331,336,341,346],{"text":299,"config":300},"About",{"href":301,"dataGaName":302,"dataGaLocation":46},"/company/","about",{"text":304,"config":305,"footerGa":308},"Jobs",{"href":306,"dataGaName":307,"dataGaLocation":46},"/jobs/","jobs",{"dataGaName":307},{"text":269,"config":310},{"href":271,"dataGaName":272,"dataGaLocation":46},{"text":312,"config":313},"Leadership",{"href":314,"dataGaName":315,"dataGaLocation":46},"/company/team/e-group/","leadership",{"text":317,"config":318},"Team",{"href":319,"dataGaName":320,"dataGaLocation":46},"/company/team/","team",{"text":322,"config":323},"Handbook",{"href":324,"dataGaName":325,"dataGaLocation":46},"https://handbook.gitlab.com/","handbook",{"text":327,"config":328},"Investor relations",{"href":329,"dataGaName":330,"dataGaLocation":46},"https://ir.gitlab.com/","investor relations",{"text":332,"config":333},"Trust Center",{"href":334,"dataGaName":335,"dataGaLocation":46},"/security/","trust center",{"text":337,"config":338},"AI Transparency Center",{"href":339,"dataGaName":340,"dataGaLocation":46},"/ai-transparency-center/","ai transparency center",{"text":342,"config":343},"Newsletter",{"href":344,"dataGaName":345,"dataGaLocation":46},"/company/contact/#contact-forms","newsletter",{"text":347,"config":348},"Press",{"href":349,"dataGaName":350,"dataGaLocation":46},"/press/","press",{"text":352,"config":353,"lists":354},"Contact us",{"dataNavLevelOne":294},[355],{"items":356},[357,360,365],{"text":53,"config":358},{"href":55,"dataGaName":359,"dataGaLocation":46},"talk to sales",{"text":361,"config":362},"Support portal",{"href":363,"dataGaName":364,"dataGaLocation":46},"https://support.gitlab.com","support portal",{"text":366,"config":367},"Customer portal",{"href":368,"dataGaName":369,"dataGaLocation":46},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":371,"login":372,"suggestions":379},"Close",{"text":373,"link":374},"To search repositories and projects, login to",{"text":375,"config":376},"gitlab.com",{"href":60,"dataGaName":377,"dataGaLocation":378},"search login","search",{"text":380,"default":381},"Suggestions",[382,384,388,390,394,398],{"text":75,"config":383},{"href":80,"dataGaName":75,"dataGaLocation":378},{"text":385,"config":386},"Code Suggestions (AI)",{"href":387,"dataGaName":385,"dataGaLocation":378},"/solutions/code-suggestions/",{"text":109,"config":389},{"href":111,"dataGaName":109,"dataGaLocation":378},{"text":391,"config":392},"GitLab on AWS",{"href":393,"dataGaName":391,"dataGaLocation":378},"/partners/technology-partners/aws/",{"text":395,"config":396},"GitLab on Google Cloud",{"href":397,"dataGaName":395,"dataGaLocation":378},"/partners/technology-partners/google-cloud-platform/",{"text":399,"config":400},"Why GitLab?",{"href":88,"dataGaName":399,"dataGaLocation":378},{"freeTrial":402,"mobileIcon":407,"desktopIcon":412,"secondaryButton":415},{"text":403,"config":404},"Start free trial",{"href":405,"dataGaName":51,"dataGaLocation":406},"https://gitlab.com/-/trials/new/","nav",{"altText":408,"config":409},"Gitlab Icon",{"src":410,"dataGaName":411,"dataGaLocation":406},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":408,"config":413},{"src":414,"dataGaName":411,"dataGaLocation":406},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":416,"config":417},"Get Started",{"href":418,"dataGaName":419,"dataGaLocation":406},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":421,"mobileIcon":425,"desktopIcon":427},{"text":422,"config":423},"Learn more about GitLab Duo",{"href":80,"dataGaName":424,"dataGaLocation":406},"gitlab duo",{"altText":408,"config":426},{"src":410,"dataGaName":411,"dataGaLocation":406},{"altText":408,"config":428},{"src":414,"dataGaName":411,"dataGaLocation":406},{"button":430,"mobileIcon":435,"desktopIcon":437},{"text":431,"config":432},"/switch",{"href":433,"dataGaName":434,"dataGaLocation":406},"#contact","switch",{"altText":408,"config":436},{"src":410,"dataGaName":411,"dataGaLocation":406},{"altText":408,"config":438},{"src":439,"dataGaName":411,"dataGaLocation":406},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":441,"mobileIcon":446,"desktopIcon":448},{"text":442,"config":443},"Back to pricing",{"href":188,"dataGaName":444,"dataGaLocation":406,"icon":445},"back to pricing","GoBack",{"altText":408,"config":447},{"src":410,"dataGaName":411,"dataGaLocation":406},{"altText":408,"config":449},{"src":414,"dataGaName":411,"dataGaLocation":406},{"title":451,"button":452,"config":457},"See how agentic AI transforms software delivery",{"text":453,"config":454},"Watch GitLab Transcend now",{"href":455,"dataGaName":456,"dataGaLocation":46},"/events/transcend/virtual/","transcend event",{"layout":458,"icon":459,"disabled":29},"release","AiStar",{"data":461},{"text":462,"source":463,"edit":469,"contribute":474,"config":479,"items":484,"minimal":690},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":464,"config":465},"View page source",{"href":466,"dataGaName":467,"dataGaLocation":468},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":470,"config":471},"Edit this page",{"href":472,"dataGaName":473,"dataGaLocation":468},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":475,"config":476},"Please contribute",{"href":477,"dataGaName":478,"dataGaLocation":468},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":480,"facebook":481,"youtube":482,"linkedin":483},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[485,532,585,629,656],{"title":186,"links":486,"subMenu":501},[487,491,496],{"text":488,"config":489},"View plans",{"href":188,"dataGaName":490,"dataGaLocation":468},"view plans",{"text":492,"config":493},"Why Premium?",{"href":494,"dataGaName":495,"dataGaLocation":468},"/pricing/premium/","why premium",{"text":497,"config":498},"Why Ultimate?",{"href":499,"dataGaName":500,"dataGaLocation":468},"/pricing/ultimate/","why ultimate",[502],{"title":503,"links":504},"Contact Us",[505,508,510,512,517,522,527],{"text":506,"config":507},"Contact sales",{"href":55,"dataGaName":56,"dataGaLocation":468},{"text":361,"config":509},{"href":363,"dataGaName":364,"dataGaLocation":468},{"text":366,"config":511},{"href":368,"dataGaName":369,"dataGaLocation":468},{"text":513,"config":514},"Status",{"href":515,"dataGaName":516,"dataGaLocation":468},"https://status.gitlab.com/","status",{"text":518,"config":519},"Terms of use",{"href":520,"dataGaName":521,"dataGaLocation":468},"/terms/","terms of use",{"text":523,"config":524},"Privacy statement",{"href":525,"dataGaName":526,"dataGaLocation":468},"/privacy/","privacy statement",{"text":528,"config":529},"Cookie preferences",{"dataGaName":530,"dataGaLocation":468,"id":531,"isOneTrustButton":29},"cookie preferences","ot-sdk-btn",{"title":91,"links":533,"subMenu":541},[534,537],{"text":23,"config":535},{"href":73,"dataGaName":536,"dataGaLocation":468},"devsecops platform",{"text":538,"config":539},"AI-Assisted Development",{"href":80,"dataGaName":540,"dataGaLocation":468},"ai-assisted development",[542],{"title":543,"links":544},"Topics",[545,550,555,560,565,570,575,580],{"text":546,"config":547},"CICD",{"href":548,"dataGaName":549,"dataGaLocation":468},"/topics/ci-cd/","cicd",{"text":551,"config":552},"GitOps",{"href":553,"dataGaName":554,"dataGaLocation":468},"/topics/gitops/","gitops",{"text":556,"config":557},"DevOps",{"href":558,"dataGaName":559,"dataGaLocation":468},"/topics/devops/","devops",{"text":561,"config":562},"Version Control",{"href":563,"dataGaName":564,"dataGaLocation":468},"/topics/version-control/","version control",{"text":566,"config":567},"DevSecOps",{"href":568,"dataGaName":569,"dataGaLocation":468},"/topics/devsecops/","devsecops",{"text":571,"config":572},"Cloud Native",{"href":573,"dataGaName":574,"dataGaLocation":468},"/topics/cloud-native/","cloud native",{"text":576,"config":577},"AI for Coding",{"href":578,"dataGaName":579,"dataGaLocation":468},"/topics/devops/ai-for-coding/","ai for coding",{"text":581,"config":582},"Agentic AI",{"href":583,"dataGaName":584,"dataGaLocation":468},"/topics/agentic-ai/","agentic ai",{"title":586,"links":587},"Solutions",[588,590,592,597,601,604,608,611,613,616,619,624],{"text":133,"config":589},{"href":128,"dataGaName":133,"dataGaLocation":468},{"text":122,"config":591},{"href":105,"dataGaName":106,"dataGaLocation":468},{"text":593,"config":594},"Agile development",{"href":595,"dataGaName":596,"dataGaLocation":468},"/solutions/agile-delivery/","agile delivery",{"text":598,"config":599},"SCM",{"href":118,"dataGaName":600,"dataGaLocation":468},"source code management",{"text":546,"config":602},{"href":111,"dataGaName":603,"dataGaLocation":468},"continuous integration & delivery",{"text":605,"config":606},"Value stream management",{"href":161,"dataGaName":607,"dataGaLocation":468},"value stream management",{"text":551,"config":609},{"href":610,"dataGaName":554,"dataGaLocation":468},"/solutions/gitops/",{"text":171,"config":612},{"href":173,"dataGaName":174,"dataGaLocation":468},{"text":614,"config":615},"Small business",{"href":178,"dataGaName":179,"dataGaLocation":468},{"text":617,"config":618},"Public sector",{"href":183,"dataGaName":184,"dataGaLocation":468},{"text":620,"config":621},"Education",{"href":622,"dataGaName":623,"dataGaLocation":468},"/solutions/education/","education",{"text":625,"config":626},"Financial services",{"href":627,"dataGaName":628,"dataGaLocation":468},"/solutions/finance/","financial services",{"title":191,"links":630},[631,633,635,637,640,642,644,646,648,650,652,654],{"text":203,"config":632},{"href":205,"dataGaName":206,"dataGaLocation":468},{"text":208,"config":634},{"href":210,"dataGaName":211,"dataGaLocation":468},{"text":213,"config":636},{"href":215,"dataGaName":216,"dataGaLocation":468},{"text":218,"config":638},{"href":220,"dataGaName":639,"dataGaLocation":468},"docs",{"text":241,"config":641},{"href":243,"dataGaName":244,"dataGaLocation":468},{"text":236,"config":643},{"href":238,"dataGaName":239,"dataGaLocation":468},{"text":246,"config":645},{"href":248,"dataGaName":249,"dataGaLocation":468},{"text":254,"config":647},{"href":256,"dataGaName":257,"dataGaLocation":468},{"text":259,"config":649},{"href":261,"dataGaName":262,"dataGaLocation":468},{"text":264,"config":651},{"href":266,"dataGaName":267,"dataGaLocation":468},{"text":269,"config":653},{"href":271,"dataGaName":272,"dataGaLocation":468},{"text":274,"config":655},{"href":276,"dataGaName":277,"dataGaLocation":468},{"title":292,"links":657},[658,660,662,664,666,668,670,674,679,681,683,685],{"text":299,"config":659},{"href":301,"dataGaName":294,"dataGaLocation":468},{"text":304,"config":661},{"href":306,"dataGaName":307,"dataGaLocation":468},{"text":312,"config":663},{"href":314,"dataGaName":315,"dataGaLocation":468},{"text":317,"config":665},{"href":319,"dataGaName":320,"dataGaLocation":468},{"text":322,"config":667},{"href":324,"dataGaName":325,"dataGaLocation":468},{"text":327,"config":669},{"href":329,"dataGaName":330,"dataGaLocation":468},{"text":671,"config":672},"Sustainability",{"href":673,"dataGaName":671,"dataGaLocation":468},"/sustainability/",{"text":675,"config":676},"Diversity, inclusion and belonging (DIB)",{"href":677,"dataGaName":678,"dataGaLocation":468},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":332,"config":680},{"href":334,"dataGaName":335,"dataGaLocation":468},{"text":342,"config":682},{"href":344,"dataGaName":345,"dataGaLocation":468},{"text":347,"config":684},{"href":349,"dataGaName":350,"dataGaLocation":468},{"text":686,"config":687},"Modern Slavery Transparency Statement",{"href":688,"dataGaName":689,"dataGaLocation":468},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":691},[692,695,698],{"text":693,"config":694},"Terms",{"href":520,"dataGaName":521,"dataGaLocation":468},{"text":696,"config":697},"Cookies",{"dataGaName":530,"dataGaLocation":468,"id":531,"isOneTrustButton":29},{"text":699,"config":700},"Privacy",{"href":525,"dataGaName":526,"dataGaLocation":468},[702],{"id":703,"title":18,"body":8,"config":704,"content":706,"description":8,"extension":27,"meta":710,"navigation":29,"path":711,"seo":712,"stem":713,"__hash__":714},"blogAuthors/en-us/blog/authors/michael-friedrich.yml",{"template":705},"BlogAuthor",{"name":18,"config":707},{"headshot":708,"ctfId":709},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659879/Blog/Author%20Headshots/dnsmichi-headshot.jpg","dnsmichi",{},"/en-us/blog/authors/michael-friedrich",{},"en-us/blog/authors/michael-friedrich","lJ-nfRIhdG49Arfrxdn1Vv4UppwD51BB13S3HwIswt4",[716,729,741],{"content":717,"config":727},{"title":718,"description":719,"authors":720,"heroImage":722,"date":723,"body":724,"category":9,"tags":725},"GitLab and Anthropic: Governed AI for enterprise development","GitLab deepens its Anthropic Claude integration, bringing governed AI, access to new models, and cloud flexibility to enterprise software development.",[721],"Stuart Moncada","https://res.cloudinary.com/about-gitlab-com/image/upload/v1776457632/llddiylsgwuze0u1rjks.png","2026-04-28","For enterprise and public sector leaders, the tension is familiar: Software teams need to move faster with AI, while security, compliance, and regulatory expectations only get more stringent. GitLab deepens its Anthropic Claude integration so organizations get access to newly released Claude models inside GitLab’s intelligent orchestration platform where governance, compliance, and auditability already run.\n\nClaude powers capabilities across GitLab Duo Agent Platform as the default model out of the box, across a variety of use cases from code generation and review to agentic chat and vulnerability resolution. If you've used GitLab Duo, you've already experienced how Duo agents automate workflows across the entire software development lifecycle (SDLC).\n\nThis accelerates the integration of Claude’s capabilities into GitLab, broadens how enterprises can deploy them, and reinforces what makes GitLab fundamentally different as a platform for software development and engineering: governance, compliance, and auditability built into every AI interaction.\n\n> \"GitLab Duo has accelerated how our teams plan, build, and ship software. The combination of Anthropic's Claude and GitLab's platform means we're getting more capable AI without changing how we work or how it is governed.\"\n>\n> – Mans Booijink, Operations Manager, Cube\n\n## The real differentiator: Governed AI\n\nWith GitLab, governance controls and auditing are built into the SDLC. When Claude suggests a code change through the GitLab Duo Agent Platform, that suggestion flows through the same merge request process, the same approval rules, the same security scanning, and the same audit trail as every other change. AI doesn't get a shortcut around your controls. It operates within them.\n\nAs GitLab moves deeper into agentic software development, where AI autonomously handles well-defined tasks, the governance layer becomes more important. An AI agent that can open a merge request, help resolve a vulnerability, or refactor a service needs to be auditable, attributable, and subject to the same policy enforcement as a human developer. That requirement is an architectural decision GitLab made from the start, and one that grows more consequential as AI agents take on broader responsibilities.\n\n## Enterprise deployment flexibility\n\nThis also expands how organizations access the latest Claude models through GitLab. Claude is available within GitLab through Google Cloud's Vertex AI and Amazon Bedrock, which means enterprises can route AI workloads through the hyperscaler commitments and cloud governance frameworks they already have in place. No separate vendor contract. No new data residency questions. Your existing Google Cloud or AWS relationship is the on-ramp. \n\nGitLab is now also available in the [Claude Marketplace](https://claude.com/platform/marketplace), allowing customers to purchase GitLab Credits and apply them toward existing Anthropic spending commitments – consolidating AI spend and simplifying how teams discover and procure GitLab alongside their Anthropic investments.\n\n## Advancing an agentic future\n\nGitLab's vision for agentic software development, where AI handles defined tasks autonomously across planning, coding, testing, securing, and deploying, requires models with strong reasoning, reliability, and safety characteristics. It also requires a platform where those autonomous actions are fully governed.\n\nAgentic workflows demand models with strong reasoning, reliability, and safety characteristics, criteria that guide how GitLab selects and integrates AI model partners. And GitLab's governance framework helps ensure that as AI agents assume more advanced development work, enterprises maintain full visibility and control over what those agents do, when they do it, and how changes are tracked.\n\n## What this means for GitLab customers\n\nIf you're already using GitLab Duo Agent Platform, you'll get access to Claude models and deeper AI assistance across your software development lifecycle, all within the governance framework you already rely on.\n\nIf you're evaluating AI-powered software development platforms, you shouldn't have to choose between advanced AI capabilities and enterprise control. This strategic integration is built to deliver both.\n\n> Want to learn more about GitLab Duo Agent Platform? [Get a demo or start a free trial today](https://about.gitlab.com/gitlab-duo-agent-platform/).",[26,726,277],"product",{"featured":29,"template":13,"slug":728},"gitlab-and-anthropic-governed-ai-for-enterprise-development",{"content":730,"config":739},{"title":731,"description":732,"authors":733,"heroImage":735,"date":736,"body":737,"category":9,"tags":738},"Give your AI agent direct, structured GitLab access with glab CLI","The GitLab CLI (glab) provides AI agents structured, reliable access to projects via the MCP, eliminating friction. This tutorial shows how you can speed up code review and issue triage.",[734],"Kai Armstrong","https://res.cloudinary.com/about-gitlab-com/image/upload/v1776347152/unw3mzatkd5xyfbzcnni.png","2026-04-27","\nWhen teams use GitLab Duo, Claude, Cursor, and other AI assistants, more of the development workflow runs through an AI agent acting on your behalf — reading issues, reviewing merge requests, running pipelines, and helping you ship faster. Most developers are already using the GitLab CLI (`glab`) from the terminal to interact with GitLab. Combining the two is a natural next step.\n\n\nThe problem is that without the right tools, AI agents are essentially guessing when it comes to your GitLab projects. They might hallucinate the details of an issue they've never seen, summarize a merge request based on stale training data rather than its actual state, or require you to manually copy context from a browser tab and paste it into a chat window just to get started. Every one of those workarounds is friction: it slows you down, introduces the possibility of error, and puts a hard ceiling on what your agent can actually do on your behalf. `glab` changes that by giving agents a direct, reliable interface to your projects.\n\n\nWith `glab`, your agent fetches what it needs directly from GitLab, acts on it, and reports back — so you spend less time relaying information and more time on the work that matters.\n\n\nIn this tutorial, you'll learn how to use `glab` to give AI agents structured, reliable access to your GitLab projects. You'll also discover how that unlocks a faster, more capable development workflow.\n\n\n## How to connect your AI agent to GitLab through MCP\n\n\nThe most direct way to supercharge your AI workflow is to give your AI agent native access to `glab` through Model Context Protocol ([MCP](https://about.gitlab.com/topics/ai/model-context-protocol/)).\n\n\n MCP is an open standard that lets AI tools discover and use external capabilities at runtime. Once connected, your AI assistant can read issues, comment on merge requests, check pipeline status, and write back to GitLab, all without copying anything from the UI or writing a single API call yourself.\n\n\n To get started, run:\n\n\n ```shell\n # Start the glab MCP server\n glab mcp serve\n ```\n\n\n Once your MCP client is configured, your AI can answer questions like *\"What's the status of my open MRs?\"* or *\"Are there any failing pipelines on main?\"* by querying GitLab directly, not scraping the web UI, not relying on stale training data. See the [full setup docs](https://docs.gitlab.com/cli/) for configuration steps for Claude Code, Cursor, and other editors.\n\n\n One detail worth knowing: `glab` automatically adds `--output json` when invoked through MCP, for any command that supports it. Your agent gets clean, structured data without you needing to think about output formats. And because `glab` uses the official MCP SDK, it stays compatible as the\n protocol evolves.\n\n\n We've also been deliberate about *which* commands are exposed through MCP. Commands that require interactive terminal input are intentionally\n excluded, so your agent never gets stuck waiting for input that will never come. What's exposed is what actually works reliably in an agent context.\n\n\n ## Let your AI participate in code review\n\n\n Most developers have a backlog of MRs waiting for review. It's one of the most time-consuming parts of the job and one of the best places to put\n AI to work. With `glab`, your agent doesn't just observe your review queue, it can work through it with you.\n\n\n ### See exactly what still needs addressing\n\n\n Start with this:\n\n\n ```shell\n glab mr view 2677 --comments --unresolved --output json\n ```\n\n\n This input returns the full MR: metadata, description, and every\n unresolved discussion, as a single structured JSON payload. Hand that to\n your AI and it has everything it needs: which threads are open, what the\n reviewer asked for, and in what context. No tab-switching, no copy-pasting\n individual comments.\n\n\n \n ```json\n {\n   \"id\": 2677,\n   \"title\": \"feat: add OAuth2 support\",\n   \"state\": \"opened\",\n   \"author\": { \"username\": \"jdwick\" },\n   \"labels\": [\"backend\", \"needs-review\"],\n   \"blocking_discussions_resolved\": false,\n   \"discussions\": [\n     {\n       \"id\": \"3107030349\",\n       \"resolved\": false,\n       \"notes\": [\n         {\n           \"author\": { \"username\": \"dmurphy\" },\n           \"body\": \"This error handling will swallow panics — consider wrapping with recover()\",\n           \"created_at\": \"2026-03-14T09:23:11.000Z\"\n         }\n       ]\n     },\n     {\n       \"id\": \"3107030412\",\n       \"resolved\": false,\n       \"notes\": [\n         {\n           \"author\": { \"username\": \"sreeves\" },\n           \"body\": \"Token refresh logic needs a test for the expired token case\",\n           \"created_at\": \"2026-03-14T10:05:44.000Z\"\n         }\n       ]\n     }\n   ]\n }\n ```\n\n\n Instead of reading through every thread yourself, you ask your agent  *\"what do I still need to fix in MR 2677?\"* and get back a prioritized summary with suggested changes. This all happens from a single command.\n\n\n ### Close the loop programmatically\n\n\n Once your AI has helped you address the feedback, it can resolve\n discussions:\n\n\n ```shell\n # List all discussions — structured, ready for the agent to process\n glab mr note list 456 --output json\n\n # Resolve a discussion once the feedback is addressed\n glab mr note resolve 456 3107030349\n\n # Reopen if something needs another look\n glab mr note reopen 456 3107030349\n ```\n\n\n\n ```json\n [\n   {\n     \"id\": 3107030349,\n     \"body\": \"This error handling will swallow panics — consider wrapping with recover()\",\n     \"author\": { \"username\": \"dmurphy\" },\n     \"resolved\": false,\n     \"resolvable\": true\n   },\n   {\n     \"id\": 3107030412,\n     \"body\": \"Token refresh logic needs a test for the expired token case\",\n     \"author\": { \"username\": \"sreeves\" },\n     \"resolved\": false,\n     \"resolvable\": true\n   }\n ]\n ```\n\n\n\n Note IDs are visible directly in the GitLab UI and API, no extra lookup needed. Your agent can work through the full list, verify each fix, and\n resolve as it goes.\n\n\n ## Talk to your AI about your code more effectively\n\n\n Even if you're not running an MCP server, there's a simpler shift that makes a huge difference: using `glab` to feed your AI better information.\n\n\n Think about the last time you asked an AI assistant to help triage issues or debug a failing pipeline. You probably copied some text from the GitLab UI and pasted it into the chat. Here's what your agent is actually\n working with when you do that:\n\n\n ```text\n open issues: 12 • milestone: 17.10 • label: bug, needs-triage ...\n ```\n\n\n Compare that to what it gets with `glab`:\n\n\n \n ```json\n [\n   {\n     \"iid\": 902,\n     \"title\": \"Pipeline fails on merge to main\",\n     \"labels\": [\"bug\", \"needs-triage\"],\n     \"milestone\": { \"title\": \"17.10\" },\n     \"assignees\": []\n   },\n   ...\n ]\n ```\n\n\n Structured, typed, complete; no ambiguity, no parsing guesswork. That's the difference between an agent that can act and one that has to ask\n follow-up questions.\n\n\n If you're using the MCP server, you get this automatically: `glab` adds `--output json` for any command that supports it. If you're working directly\n from the terminal, just add the flag yourself:\n\n\n ```shell\n # Pull open issues for triage\n glab issue list --label \"needs-triage\" --output json\n\n # Check pipeline status\n glab ci status --output json\n\n # Get full MR details\n glab mr view 456 --output json\n ```\n\n\n We've significantly expanded JSON output support in recent releases. It now covers CI status, milestones, labels, releases, schedules, cluster agents, work items, MR approvers, repo contributors, and more. If `glab` can\n retrieve it, your AI can consume it cleanly.\n\n\n ### A real workflow\n\n\n ```shell\n $ glab issue list --label \"needs-triage\" --milestone \"17.10\"\n --output json\n ```\n\n\n ```text\n Agent: I found 2 unassigned bugs in the 17.10 milestone that need triage:\n 1. #902 — Pipeline fails on merge to main (opened 5 days ago)\n 2. #903 — Auth token not refreshing on expiry (opened 4 days ago)\n Both are unassigned. Want me to draft triage notes and suggest assignees based on recent commit history?\n ```\n\n\n ## Your agent is never limited to built-in commands\n\n\n `glab`'s first-class commands cover the most common workflows, but your agent is never limited to them. Through `glab api`, it has authenticated access to the full GitLab REST and GraphQL API surface, using the same session, with no extra credentials or configuration required.\n\n\n This is a meaningful differentiator. Most CLI tools stop at what their commands expose. With `glab`, if GitLab's API supports it, your agent can do it. It's always working from a trusted, authenticated context.\n\n\n A practical example: fetching just the list of changed files in an MR before deciding which diffs to pull in full:\n\n\n ```shell\n # Get changed file paths — lightweight, no diff content yet\n glab api \"/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/diffs?per_page=100\" \\\n | jq '.[].new_path'\n\n# Then fetch only the specific file your agent needs\nglab api \"/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/diffs?per_page=100\" \\\n| jq '.[] | select(.new_path == \"path/to/file.go\")'\n ```\n\n\n ```text\n \"internal/auth/token.go\"\n \"internal/auth/token_test.go\"\n \"internal/oauth/refresh.go\"\n ```\n\n\n For anything the REST API doesn't cover (epics, certain work item queries, complex cross-project data),  `glab api graphql` gives you the full\n GraphQL interface:\n\n\n ```shell\n   glab api graphql -f query='\n {\n   project(fullPath: \"gitlab-org/gitlab\") {\n     mergeRequest(iid: \"12345\") {\n       title\n       reviewers { nodes { username } }\n     }\n   }\n }'\n ```\n\n ```json\n{\n   \"data\": {\n     \"project\": {\n       \"mergeRequest\": {\n         \"title\": \"feat: add OAuth2 support\",\n         \"reviewers\": {\n           \"nodes\": [\n             { \"username\": \"dmurphy\" },\n             { \"username\": \"sreeves\" }\n           ]\n         }\n       }\n     }\n   }\n }\n\n ```\n\n\n Your agent has a single, authenticated entry point to everything GitLab exposes without the token juggling, separate API clients, or configuration\n overhead.\n\n\n ## What's coming and your feedback\n\n\n Two improvements we're actively working on will make `glab` even more useful for agent workflows:\n\n\n **Agent-aware help text.** Today, `--help` output is written for humansvat a terminal. We're updating it to surface the non-interactive alternative\n for every interactive command, flag which commands support `--output json`, and generally make help a useful resource for agents discovering\n capabilities at runtime — not just humans.\n\n\n **Better machine-readable errors.** When something goes wrong today, agents get the same human-readable error messages as terminal users. We're\n changing that so errors in JSON mode return structured output, giving your agent the information it needs to handle failures gracefully, retry intelligently, or surface the right context back to you.\n\n\n Both of these are in active development. If you're already using `glab` with an AI tool, you're exactly the audience we want feedback from.\n\n\n * **What friction are you hitting?** Commands that don't behave well in agent contexts, error messages that aren't actionable, gaps in JSON output\n coverage. We want to know.\n\n * **What workflows have you unlocked?** Real usage patterns help us prioritize what to build next.\n\n\n Join the discussion in [our feedback issue](https://gitlab.com/gitlab-org/cli/-/issues/8177) — that's where we're shaping the roadmap for agent-friendliness, and where your input will have the most direct impact. If you've found a specific gap, [open an issue](https://gitlab.com/gitlab-org/cli/-/issues/new). If you've got a fix in mind, contributions are welcome. Visit [CONTRIBUTING.md](https://gitlab.com/gitlab-org/cli/-/blob/main/CONTRIBUTING.md) to get started.\n\n\n The GitLab CLI has always been about giving developers more control over their workflow. As AI becomes a bigger part of how we all work, that means making `glab` the best possible interface between your AI tools and your GitLab projects. We're just getting started and we'd love to build the next part with you.\n",[26,726,24],{"featured":29,"template":13,"slug":740},"give-your-ai-agent-direct-structured-gitlab-access-with-glab-cli",{"content":742,"config":750},{"title":743,"description":744,"authors":745,"heroImage":735,"date":747,"body":748,"category":9,"tags":749},"GitHub Copilot's new policy for AI training is a governance wake-up call","Learn what GitHub's Copilot policy change means for regulated industries, and why GitLab's commitment to customer data privacy matters.",[746],"Allie Holland","2026-04-20","GitHub recently [announced](https://github.blog/news-insights/company-news/updates-to-github-copilot-interaction-data-usage-policy/) a significant change to how it handles data from Copilot users. Starting April 24, 2026, interaction data from Copilot Free, Pro, and Pro+ users, including inputs, outputs, code snippets, and associated context, will be used to train AI models by default, unless users actively opt out. Copilot Business and Enterprise customers are exempt under existing contract terms.\n\nFor organizations in regulated industries, including finance, healthcare, defense, and public sector, the policy shift raises questions that go beyond individual developer preferences. It forces a harder look at a question that engineering and security leaders should be asking every AI vendor in their stack: Do you train on our code? \n\nGitLab's answer is no. GitLab does not train AI models on customer code at any tier, and AI vendors are contractually prohibited from using customer inputs or outputs for their own purposes. The [GitLab AI Transparency Center](https://about.gitlab.com/ai-transparency-center/) makes that commitment auditable: a single location documenting which models power which features, how data is handled, subprocessor relationships, and data retention periods. The GitLab AI Transparency Center also lists the compliance status of each feature, including confirmation that GitLab's current AI features do not qualify as high-risk systems under the EU AI Act. It's a standard GitLab CEO Bill Staples has consistently [reiterated](https://www.linkedin.com/posts/williamstaples_gitlab-1810-agentic-ai-now-open-to-even-activity-7443280763715985408-aHxf?utm_source=share&utm_medium=member_desktop&rcm=ACoAABsu7EUBcb_a1-JHKS9RC0B5rf8Ye-5XM60) and one reflected in GitLab's mission and [Trust Center](https://trust.gitlab.com/).\n\n## What the policy change actually means\n\nGitHub's announcement also specifies that the data may be shared with GitHub affiliates, including Microsoft, for AI development purposes.\n\nA policy change of this nature forces organizations to re-examine their AI governance posture, audit their Copilot license tiers, and confirm that the right controls are configured across their teams.\n\n## Why AI governance matters in regulated environments\n\nSource code is often among an organization's most sensitive intellectual property. It may contain references to internal systems, reflect proprietary business logic, or touch data flows governed by strict retention and access policies. When that code passes through an AI assistant, questions about training data usage, model vendor relationships, and data residency become compliance concerns.\n\nThe exposure is particularly acute for financial services firms that have invested in proprietary algorithms, fraud detection logic, credit risk models, underwriting rules, trading strategies. When AI tooling processes that code and uses it to train models serving competitors, vendor data practices become an IP concern.\n\nFinancial institutions operating under [the Federal Reserve's Supervisory Guidance on Model Risk Management (SR 11-7) and the](https://www.federalreserve.gov/supervisionreg/srletters/sr1107.htm) [Digital Operational Resilience Act (DORA)](https://eur-lex.europa.eu/eli/reg/2022/2554/oj/eng) are required to maintain documented, auditable oversight of third-party technology providers, including understanding how those providers handle data. Third-party AI tools used in development workflows increasingly fall within the scope of model risk oversight, and material changes to vendor data practices require updated documentation. \n\nIn the public sector, [the National Institute of Standards and Technology Special Publication 800-53 (NIST 800-53)](https://csrc.nist.gov/publications/detail/sp/800-53/rev-5/final) and the [Federal Information Security Modernization Act (FISMA)](https://www.cisa.gov/topics/cyber-threats-and-advisories/federal-information-security-modernization-act) establish that sensitive or classified code must never leave a controlled boundary. For U.S. Department of Defense and intelligence community environments in particular, a vendor's default data posture is an operational concern. In healthcare, [the Health Insurance Portability and Accountability Act (HIPAA)](https://www.hhs.gov/hipaa/index.html) governs how patient-adjacent data is handled by third parties, and development environments that touch clinical systems increasingly fall within that scope.\n\nAcross all of these contexts, the common thread is the same: A vendor policy that changes data usage defaults, requires individual opt-out, and offers different protections depending on account tier introduces exactly the kind of uncontrolled variable that compliance teams cannot afford.\n\n## What regulated industries actually need from AI vendors\n\nRegulated organizations have largely moved past debating whether to adopt AI in development workflows. The focus now is on doing so in a way they can defend to regulators, boards, and customers. That shift has surfaced a consistent set of requirements regardless of sector.\n\n**Contractual certainty.** Regulated firms need to know, with specificity, what happens to their data. A clear, documented, unconditional commitment is what's required, not something that varies by plan or requires action before a deadline.\n\n**Auditability.** Model risk management frameworks require organizations to understand and validate the AI systems they deploy, including the training data behind those models and the third parties involved in their development. Vendors who cannot answer these questions create documentation risk for the organizations relying on them.\n\n**Separation from vendor incentives.** When an AI vendor trains models on customer usage data, code and workflows become inputs to a system that also serves competitors. For institutions with proprietary trading logic, underwriting models, or fraud detection systems, that's a genuine IP exposure.\n\n## GitLab's position on AI data governance\n\nGitLab does not use customer code to train AI models. This commitment applies at every tier, and AI vendors are contractually prohibited from using inputs or outputs associated with GitLab customers for their own purposes.\n\nThis is a deliberate architectural and policy choice, not a feature of a particular pricing tier. As GitLab's [post on enterprise independence](https://about.gitlab.com/blog/why-enterprise-independence-matters-more-than-ever-in-devsecops/) notes, data governance has become \"an increasingly critical factor in enterprise technology decisions, driven by a complex web of national and regional data protection laws and growing concern about control over sensitive intellectual property.\"\n\nGitLab is also cloud-neutral and model-neutral while supporting self-hosted deployments, not commercially tied to any single cloud provider or large language model (LLM). That i[ndependence matters](https://about.gitlab.com/blog/why-enterprise-independence-matters-more-than-ever-in-devsecops/) for regulated organizations evaluating vendor concentration risk. The [AI Continuity Plan](https://handbook.gitlab.com/handbook/product/ai/continuity-plan/) documents how vendor changes are managed, including material changes to how AI vendors treat customer data, a direct response to the governance requirements under frameworks like [DORA](https://handbook.gitlab.com/handbook/legal/dora/). \n\n## The governance gap AI teams need to close\n\nGitHub's policy update is a reminder that for organizations in regulated industries, understanding exactly how an AI tool handles data is a prerequisite for using it at all. That means asking vendors for clear, documented answers: Is our data used for model training? Who are your AI model subprocessors? What happens if a vendor changes its data practices? Can we deploy in a way that keeps all AI processing within our own infrastructure? What indemnification do you offer for AI-generated output?\n\nVendors who can answer those questions clearly, and document those answers in an auditable form, are vendors you can build on. **Those who cannot will create compliance debt every time they ship a policy update.** And when a vendor can change its data practices with 30 days notice, that's not a partnership built for regulated industries. That's a liability.\n\n> Learn more about GitLab's approach to AI governance at the [GitLab AI Transparency Center](https://about.gitlab.com/ai-transparency-center/).",[26,726],{"featured":12,"template":13,"slug":751},"github-copilots-new-policy-for-ai-training-is-a-governance-wake-up-call",{"promotions":753},[754,767,778,790],{"id":755,"categories":756,"header":757,"text":758,"button":759,"image":764},"ai-modernization",[9],"Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":760,"config":761},"Get your AI maturity score",{"href":762,"dataGaName":763,"dataGaLocation":244},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":765},{"src":766},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":768,"categories":769,"header":770,"text":758,"button":771,"image":775},"devops-modernization",[726,569],"Are you just managing tools or shipping innovation?",{"text":772,"config":773},"Get your DevOps maturity score",{"href":774,"dataGaName":763,"dataGaLocation":244},"/assessments/devops-modernization-assessment/",{"config":776},{"src":777},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":779,"categories":780,"header":782,"text":758,"button":783,"image":787},"security-modernization",[781],"security","Are you trading speed for security?",{"text":784,"config":785},"Get your security maturity score",{"href":786,"dataGaName":763,"dataGaLocation":244},"/assessments/security-modernization-assessment/",{"config":788},{"src":789},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":791,"paths":792,"header":795,"text":796,"button":797,"image":802},"github-azure-migration",[793,794],"migration-from-azure-devops-to-gitlab","integrating-azure-devops-scm-and-gitlab","Is your team ready for GitHub's Azure move?","GitHub is already rebuilding around Azure. Find out what it means for you.",{"text":798,"config":799},"See how GitLab compares to GitHub",{"href":800,"dataGaName":801,"dataGaLocation":244},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":803},{"src":777},{"header":805,"blurb":806,"button":807,"secondaryButton":812},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":808,"config":809},"Get your free trial",{"href":810,"dataGaName":51,"dataGaLocation":811},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":506,"config":813},{"href":55,"dataGaName":56,"dataGaLocation":811},1777576650164]