Skip to content

feat: add snippet CRUD tools - #470

Open
renanliberato wants to merge 8 commits into
zereight:mainfrom
renanliberato:feat/snippets
Open

feat: add snippet CRUD tools#470
renanliberato wants to merge 8 commits into
zereight:mainfrom
renanliberato:feat/snippets

Conversation

@renanliberato

@renanliberato renanliberato commented May 12, 2026

Copy link
Copy Markdown

Adds list_snippets, get_snippet, create_snippet, update_snippet, and delete_snippet — supporting both project-scoped and personal snippets via optional project_id. Registered under a new non-default "snippets" toolset.

It seems well covered and tested:

  • unit tests
  • exhaustive LLM code review
  • llm session tests in a gitlab instance I have access with a prompt list ⬇️
Prompt Tool Result
P1 discover_tools ✅ snippets active
P2 create_snippet (personal, single-file) ✅ id=****
P3 get_snippet (no content) ✅ no content field
P4 get_snippet (with content) ✅ content correct
P5 update_snippet (title+visibility) ✅ updated
P6 update_snippet (content, no file_name) ✅ rejected at parse
P7 update_snippet (file_name only) ✅ rejected at parse
P8 update_snippet (file+content) + verify ✅ content updated
P9 list_snippets (personal) ✅ id/title/web_url present
P10 delete_snippet + 404 verify ✅ deleted, 404 confirmed
P11 create_snippet (mixed shape) ✅ rejected at parse
P12 create_snippet (no shape) ✅ rejected at parse
P13 create_snippet (multi-file, project) ✅ id=****, 2 files
P14 get_snippet multi-file (no content) ✅ no content on files
P15 get_snippet multi-file (with content) ✅ per-file content, no root content
P16 update one file + verify other unchanged ✅ selective update works
P17 list_snippets (project) ✅ id=**** present
P18 delete_snippet multi-file ✅ deleted
P19 double-delete 404 ✅ error propagated

My first pull request here, let me know anything I missed.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7e5f431e08

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread index.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ee5e4724ff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread index.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4ff40ace3f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread index.ts Outdated

@zereight zereight left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding the snippet toolset. I found a few API-contract issues that should be fixed before merging.

  1. Nested multi-file snippet content fetch can 404

getSnippetFileRawContent() encodes the file path per path segment:

filePath.split("/").map(encodeURIComponent).join("/")

For a nested snippet file like dir/policy.md, the request stays as /files/main/dir/policy.md/raw. GitLab's snippets API expects file_path to be a URL-encoded path for the /files/:ref/:file_path/raw endpoint, so nested paths can be routed incorrectly or return 404.

Suggested fix: encode the entire path with encodeURIComponent(filePath) and add a test with a nested file path such as dir/policy.md.

  1. URL-encoded project paths are double-encoded

getSnippetsEndpoint() accepts project_id values described as either project IDs or URL-encoded paths, but it passes the value directly through encodeURIComponent. With project_id=group%2Fproject, the endpoint becomes /projects/group%252Fproject/snippets instead of the intended project path.

Suggested fix: follow the existing project endpoint pattern and decode before re-encoding, e.g. decodeURIComponent(projectId) before getEffectiveProjectId/encodeURIComponent.

  1. Project snippet responses may fail schema parsing

GitLabSnippetSchema requires visibility, but the Project Snippets API response shape does not consistently document/return that field. A valid project snippet response without visibility would fail with a ZodError across list/get/create/update paths.

Suggested fix: make visibility optional or split personal/project snippet response schemas.

  1. update_snippet does not expose the modern multi-file update contract

create_snippet supports files[], but update_snippet only exposes the legacy-ish file_name + content shape and converts it to a single action: "update" file. GitLab supports multi-file update actions such as create/update/delete/move with previous_path. As written, users can create multi-file snippets but cannot fully update/move/delete files through this tool.

Suggested fix: add a files[] update shape with action and previous_path support, while keeping file_name/content as a backward-compatible shortcut if desired.

renanliberato and others added 4 commits May 15, 2026 16:49
Adds list_snippets, get_snippet, create_snippet, update_snippet, and
delete_snippet — supporting both project-scoped and personal snippets
via optional project_id. Registered under a new non-default "snippets"
toolset.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
The multi-file content endpoint requires a ref to construct the API URL.
Using default_branch caused 404s on instances where the default branch
is not "main"; fetching raw_url directly also 404s because it is a web
URL, not an API endpoint.

GitLab embeds the correct ref inside each file's raw_url
(.../raw/{ref}/{path}). Parse it out and pass it to the REST API
endpoint (/snippets/{id}/files/{ref}/{path}/raw) instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getSnippetFileRawContent now takes an explicit ref string instead of a
raw_url to parse. The ref-extraction logic is factored into a standalone
extractSnippetRef helper, resolved once at the call site rather than
once per file inside the fetch function.

get_snippet also gains an optional ref parameter (matching get_file_contents)
so callers can override the ref directly without relying on raw_url parsing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Nested multi-file paths now encode the full file_path as a single segment
  (encodeURIComponent) per GitLab's /files/:ref/:file_path/raw contract.
  Previously, paths like dir/policy.md kept the slash and 404'd.
- getSnippetsEndpoint decodes URL-encoded project paths before re-encoding,
  matching the existing pattern in merge_requests/issues. Eliminates
  double-encoded URLs like /projects/group%252Fproject/snippets.
- GitLabSnippetSchema.visibility is now optional — the project snippets API
  does not consistently return it and ZodError was breaking list/get/create/
  update for valid responses.
- update_snippet exposes the modern multi-file contract: files[] with
  action (create/update/delete/move) and previous_path. The file_name+content
  shortcut remains for backward compatibility and is mutually exclusive.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8a34d885f8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread index.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a5314e83f0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread schemas.ts
SnippetFileUpdateActionSchema had previous_path optional even though the
schema description said it was required for action: "move". That left
malformed move actions to fail late with a GitLab 400 instead of being
caught at parse time. Adds a superRefine that surfaces the constraint as
a Zod error so LLM-generated update requests get a clear, local rejection.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 68b4aa690a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread index.ts
Comment thread schemas.ts

@zereight zereight left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the follow-up fixes. The original API-contract issues I raised are mostly addressed now: nested snippet file paths are encoded correctly, URL-encoded project paths are decoded before re-encoding, visibility is no longer required for project snippet responses, and update_snippet now exposes the modern files[] update shape. The new test/test-snippets.ts file is also wired into test:mock.

I’m refreshing the request-changes baseline because a few issues remain:

  1. Project-scoped deployments still need a guard for omitted project_id. When GITLAB_PROJECT_ID or GITLAB_ALLOWED_PROJECT_IDS is configured, falling back to the global /snippets endpoint bypasses the project boundary. I replied on the existing thread with the expected project-standard behavior.

  2. get_snippet(include_content=true, ref=...) still requires raw_url before it checks the explicit ref, so valid calls can fail when the snippet file payload has path but no raw_url. I replied on the existing thread with the minimal shape.

  3. SnippetFileUpdateActionSchema should validate the action-specific required fields locally: file_path for every action, content for create/update, previous_path for move, and a non-empty files[] array when provided. I replied on the existing schema thread with the matrix.

Once these are addressed, this should be close to merge-ready.

get_snippet(include_content=true) previously threw if files[0].raw_url
was missing, even when the caller passed ref explicitly. Flip the guard
so raw_url is only required as a fallback for inferring ref.
getSnippetsEndpoint used to fall back to the global /snippets endpoint
whenever project_id was omitted, bypassing GITLAB_PROJECT_ID and
GITLAB_ALLOWED_PROJECT_IDS access boundaries. Route omitted project_id
through getEffectiveProjectId when either scope env is set, reusing its
existing default-when-unambiguous, reject-when-ambiguous semantics.
Personal snippets remain available only when no scope is configured.
SnippetFileUpdateActionSchema only enforced previous_path for 'move',
so malformed actions (create/update without content, delete without
file_path) and empty files[] arrays passed local validation and only
failed later as GitLab 400s. Expand the superRefine with the full
action matrix and require files[] to be non-empty when provided.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 79eb6d6f57

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread index.ts

@zereight zereight left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The three follow-ups from the previous review look fixed on 79eb6d6f:

  • Omitted project_id now routes through getEffectiveProjectId when GITLAB_PROJECT_ID or GITLAB_ALLOWED_PROJECT_IDS is set, instead of falling back to /snippets.
  • Multi-file get_snippet(include_content=true) accepts an explicit ref without requiring files[].raw_url.
  • SnippetFileUpdateActionSchema rejects malformed files[] actions at parse time, with tests for the action matrix.

One gap remains. In get_snippet, when files.length <= 1, content fetch always uses /raw and ignores args.ref. Multi-file snippets already honor ref via getSnippetFileRawContent, so ref behaves differently depending on file count. Callers passing include_content=true and ref on a single-file snippet still get default HEAD content.

Suggested fix: when args.ref is set and there is exactly one file, resolve the path from files[0].path or snippet.file_name and call getSnippetFileRawContent; keep /raw as the fallback when ref is omitted.

Minor follow-up (non-blocking): snippet schema descriptions say "omit for personal snippets" but do not mention scope env behavior. Other tools use "(optional when GITLAB_PROJECT_ID is set)" — worth aligning so copy-paste configs match runtime.

test/test-snippets.ts coverage is solid and the toolset wiring looks correct. Once single-file ref is handled, this should be good to merge.

Comment thread index.ts
}))
);
} else {
result.content = await getSnippetRawContent(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

args.ref is ignored here because this branch always calls getSnippetRawContent. Use getSnippetFileRawContent when ref is provided and there is a resolvable file path (files[0].path or snippet.file_name).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants