Skip to content

fix(issues): remove a weight with null and publish 0 as a weight - #1805

Merged
jmrplens merged 4 commits into
mainfrom
issue-1390-issue-weight-null
Nov 11, 2026
Merged

jmrplens merged 4 commits into
mainfrom
issue-1390-issue-weight-null

Conversation

@jmrplens

@jmrplens jmrplens commented Nov 11, 2026 •

Copy link
Copy Markdown
Owner

issue.update offered null for the weight in its schema and read it as the field left out, so a weight could be set to 0 but never removed. The pointer it decoded into cannot tell a JSON null from an absent key.

I added toolutil.Nullable[T], a three-state input: absent leaves the value alone, null removes it, and any value, 0 included, sets it. It publishes the same schema the pointer did (["null","integer"] through TypeSchemas), and UnmarshalParams coerces a numeric string into it as it did into the pointer.

issue.update sends a null weight through client-go's ResetWeight, which writes {"weight": null}. issue.work_item_update and group.epic_update send weightWidget {weight: null} through the request option of toolutil.ApplyWorkItemWidgets, because client-go's weight widget is tagged omitempty and GitLab refuses a widget without the key. I measured it on GitLab EE 19.5.0: that is the only body that removes an epic's weight, 0 is stored as the weight 0, and {} is refused.

The weight GitLab answers with is now read off the captured response as a pointer wherever an issue is published: the issue outputs, search and the merge request issue lists through toolutil.IssueBasicExtra, the issues of a link and the related issues, the issues of a project and a group milestone, and a to-do's target. client-go decodes it into an int64, so 0 and null both read as 0 (upstream register "A measure GitLab sends as null decodes as 0"), and the outputs tagged it omitempty, so a weight of 0 was never published. client-go v3.46.1 still decodes Issue.Weight, IssueRelation.Weight and TodoTarget.Weight as int64 and still tags the weight widget omitempty, so both the captures and the request option stay. The issue card shows the weight, 0 included.

The clearing gate probes a field typed Nullable with null rather than 0, since its 0 is a value, and the 1:1 audit declares the six captured weights as kept for their null, beside the group board's weight capture the client-go bump added.

The issue package's action IDs are written out rather than concatenated, which gremlins reported as 17 uncovered mutants, and TestActionIDs_AreTheDomainPlusTheSpecName holds each to the domain plus its spec name.

Two more commits take the two packages the layer touches that held conditions gobco found evaluated one way only, all of them older than the layer, to every condition evaluated both ways (GOBCO_GATE=all). In the 1:1 audit's sdk walk, two are redundant and go (an assignment always names a target; a variable assigned itself adds an alias that changes nothing) and three get the fixtures they lacked: a raw request built straight into a field, and a body edit whose Drop a function of the package's own returns. In toolutil, two cannot go the other way and go: the meta dispatcher's tier check guarded a route lookup the envelope's action enum already guarantees, and titleLink checked for a blank title its callers never hand it beside an address.

This is the first layer of a three-layer stack on main, after the client-go v3.46.1 bump (#1671) and Go 1.27.4 (#1800). Its last commit regenerates what the layer moves: the tool snapshots, the tool reference of the issue and group pages in both languages, llms, the token footprint, the report-only 1:1 baseline and the testing reference.

What I ran on this layer's head, with Go 1.27.4: the unit suite with the snapshot parity on, golangci-lint run --build-tags e2e on the touched packages, every target of the Generated artifacts job, check-request-inventory, check-e2e-static, make audit-1to1, check-file-headers and check-dco, and GOBCO_GATE=all on toolutil (4596 of 4596 conditions) and cmd/audit_1to1/internal/sdk (1572 of 1572). At the stack's top, 100% statement coverage and gremlins at Lived 0, Not covered 0 and Timed out 0 on every package this layer touches: toolutil (2184 killed), cmd/audit_1to1/internal/sdk (848), issues (228), epics (199), workitems (114), groupmilestones (81), milestones (71), issuelinks (50) and todos (50). The complete Docker runs of the stack's top passed on GitLab 19.5.4, on a tree that differs from it only in a 1:1 audit declarations file the server binary does not build: make test-e2e-ce (common and ce, 1450 tests, no failures, the one declared skip, the throttle probe, run last on the same stack and passed) and make test-e2e-ee (common and ee, licensed Ultimate, 1802 tests, no skips, no failures). The weight scenarios need a licensed instance, so they ran in the EE run, on every surface: TestIssue_Update_WeightNullRemovesTheWeight, TestIssue_Update_WeightZeroIsAWeight, TestWorkItems_Update_WeightNullRemovesTheWeight and TestEpics_Update_WeightNullRemovesTheWeight.

Closes #1390
Part of #1306

issue.update offered null for the weight in its schema and read it as
the field left out, so a weight could be set to 0 but never removed.
The pointer it was decoded into cannot tell a JSON null from an absent
key. toolutil.Nullable[T] can: absent leaves the value alone, null
removes it and any value, 0 included, sets it. It publishes the schema
the pointer did, and UnmarshalParams coerces a numeric string into it
as it did into the pointer.

issue.update sends a null weight through client-go's ResetWeight, which
writes {"weight": null}. issue.work_item_update and group.epic_update
send weightWidget {weight: null} through the request option of
toolutil.ApplyWorkItemWidgets, because client-go's weight widget is
tagged omitempty and GitLab refuses a widget without the key. Measured
on GitLab EE 19.5.0: that is the only body that removes an epic's
weight, 0 is stored as the weight 0, and {} is refused.

The weight GitLab answers with is read off the captured response as a
pointer wherever an issue is published: the issue outputs, search and
the merge request issue lists through toolutil.IssueBasicExtra, the
issues of a link and the related issues, the issues of a project and a
group milestone and a to-do's target. client-go decodes it into an
int64, so 0 and null both read as 0, and the outputs tagged it
omitempty, so a weight of 0 was never published. The issue card shows
the weight, 0 included.

The clearing gate probes a field typed Nullable with null rather than
0, since its 0 is a value, and the 1:1 audit declares the six captured
weights as kept for their null.

Closes #1390

Signed-off-by: José M. Requena Plens <mail@jmrp.io>
…h ways

gobco found three conditions of the raw request walk and two of the body
edit walk that no test made go both ways. Two are redundant and go: an
assignment and a declaration always name a target, so bind never sees an
empty left-hand side, and a variable assigned itself adds an alias that
gives it the place it already holds, which assign leaves alone.

The other three are cases no fixture had: a raw request built straight
into a field, which binds no variable, so a call of Do that reads it
back through a method is not told it, and a body edit whose Drop is
returned by a function of the package's own, whose arguments are not
read as the keys it drops.

Signed-off-by: José M. Requena Plens <mail@jmrp.io>
gobco reported two conditions of toolutil evaluated one way only, and
neither can go the other. A meta call names an action the envelope's
enum lists before the handler runs, so the tier check needs no lookup
guard: the zero route an unknown action would read prunes nothing. And
titleLink is handed a title that is the address whenever the title is
blank, so a blank title beside an address never reaches the code span
branch.

Signed-off-by: José M. Requena Plens <mail@jmrp.io>
The weight of issue.update, issue.work_item_update and group.epic_update now takes null, and the issue outputs publish a weight of 0, which moves the tool snapshots, the tool reference of the issue and group pages in both languages, llms, the token footprint, the report-only 1:1 baseline and the testing reference.

Signed-off-by: José M. Requena Plens <mail@jmrp.io>
@sourcery-ai

sourcery-ai Bot commented Nov 11, 2026

Copy link
Copy Markdown

Sorry @jmrplens, your pull request is larger than the review limit of 150,000 diff characters

@github-actions github-actions Bot added tooling The audit and generator commands under cmd/, and the Makefile targets that run them e2e The end-to-end suite and its Docker fixtures labels Nov 11, 2026
@jmrplens
jmrplens added this pull request to stack #1808 November 11, 2026 22:13
@jmrplens jmrplens added bug Something isn't working tools The GitLab tool surface. Issue-form area only: two thirds of pull requests touch it, so no path rule v3.5.0 Targeted at the 3.5.0 release labels Nov 11, 2026
@jmrplens jmrplens added this to the 3.5.0 milestone Nov 11, 2026
@jmrplens jmrplens changed the title issue 1390 issue weight null fix(issues): remove a weight with null and publish 0 as a weight Nov 11, 2026
@jmrplens jmrplens closed this Nov 11, 2026
@jmrplens jmrplens reopened this Nov 11, 2026
@coderabbitai

coderabbitai Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Explain Change →

Summary

Summary by CodeRabbit

  • New Features
    • Update epic, issue, and work-item weights with 0 as a valid value, null to remove a weight, or omission to leave it unchanged.
    • Weight displays now distinguish an explicit zero from an unset weight across issue, milestone, and related-item results.
  • Bug Fixes
    • Corrected weight reporting where missing values could previously appear as zero.
    • Improved link display for titles containing only whitespace.
  • Documentation
    • Clarified weight behavior and refreshed token-footprint and testing statistics.

The changes add nullable weight handling for updates and outputs across issue, epic, work-item, milestone, issue-link, and to-do tools. They also update tool schemas and reference material, add tests, and revise audit-analysis and utility behavior.

Changes: Weight Semantics

Layer / File(s) Summary
Nullable values and shared widget handling
internal/toolutil/nullable.go, internal/toolutil/schema_type_overrides.go, internal/toolutil/meta_tool.go, internal/toolutil/work_item_widgets.go, internal/tools/action_specs_test.go, internal/toolutil/nullable_test.go, internal/toolutil/work_item_widgets_test.go
A new toolutil.Nullable[T] represents omitted, null, and supplied values. Schema generation and numeric coercion support nullable numbers. Shared work-item handling sends null for removal, accepts zero, and rejects negative weights.
Nullable weight updates
internal/tools/issues/issues.go, internal/tools/issues/issues_test.go, internal/tools/epics/epics.go, internal/tools/epics/epics_test.go, internal/tools/workitems/work_items.go, internal/tools/workitems/work_items_test.go, test/e2e/gitlab/ee/*, docs/development/audit-1to1-baseline.json
Issue, epic, and work-item update inputs distinguish omission, null, and numeric weights. Tests cover these values, including updates that send null through parameter decoding. End-to-end tests cover zero and null updates.
Preserve nullable weights in outputs
internal/toolutil/sent_shapes.go, cmd/audit_1to1/internal/sdk/*, internal/tools/issues/*, internal/tools/groupmilestones/*, internal/tools/milestones/*, internal/tools/issuelinks/*, internal/tools/todos/*
Output conversions use captured response weights where the SDK decodes null as zero. Tests check that zero remains present, null is omitted, and capture errors are returned. Issue Markdown output includes a weight when it is non-null, including zero.
Weight schemas and published documentation
internal/tools/testdata/tools_*.json, llms-full-individual-tools.txt, site/src/content/docs/*/reference/tools/*, README.md, docs/development/token-footprint.md, site/src/data/token-footprint.json, docs/development/testing/testing.md
Tool schemas and reference text describe null removal, omission, and zero-weight behavior. Token-footprint values and test-count tables are refreshed.

Audit and Utility Updates

Layer / File(s) Summary
Raw-read and body-edit analysis
cmd/audit_1to1/internal/sdk/raw_reads.go, cmd/audit_1to1/internal/sdk/raw_reads_test.go, cmd/audit_1to1/internal/sdk/upstream_waits_test.go
Raw-read analysis retains self-aliases and binds requests without an empty-target check. Fixtures cover request fields, self-assignment, and body-edit keys from a package-local helper.
Action IDs and utility behavior
internal/tools/issues/action_specs.go, internal/tools/issues/action_specs_test.go, internal/toolutil/meta_tool.go, internal/toolutil/text.go
Issue action IDs are written as literals and tested against spec names. The meta-tool tier-refusal lookup and title-link formatting also change.

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant ToolClient
  participant UpdateInput
  participant ApplyWorkItemWidgets
  participant GitLab
  ToolClient->>UpdateInput: Send omitted, null, or numeric weight
  UpdateInput->>ApplyWorkItemWidgets: Pass nullable weight
  ApplyWorkItemWidgets->>GitLab: Send weight value or null
Loading

Possibly related issues

  • Issue 1306: The nullable input type addresses the field-presence handling described in the issue.
  • Issue 1390: The changes cover explicit null removal and preserve zero as a published weight, as described in the issue.

Suggested reviewers: sourcery-ai

Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title names the two behaviors the PR changes: null removes an issue weight, and weight 0 is published. It matches the changeset, which adds Nullable[T], sends null weight updates through client-go…
Description check Passed The description follows the template's main sections: a summary of the change, the related issue (Closes #1390, Part of #1306), breaking-change notes (not stated, but the change is an internal input-t…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title names the two behaviors the PR changes: null removes an issue weight, and weight 0 is published. It matches the changeset, which adds Nullable[T], sends null weight updates through client-go and work-item widget paths, and publishes captured zero weights in outputs. It does not name the wider captured-response and audit-gate work, which is expected for a title.

Full details: Description check

Explanation

The description follows the template's main sections: a summary of the change, the related issue (Closes #1390, Part of #1306), breaking-change notes (not stated, but the change is an internal input-type change), and a detailed validation account. It does not fill the Type of Change checkboxes, the Changes Made bullets, the How to Test steps, or the checklist, so the template is only partly followed. The content is specific and substantive, so the description is mostly complete.

Finishing Touches

Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Review coverage is incomplete: 2 files could not be fully reviewed. Findings from completed review steps are included; see review info for details.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

Recent review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 257c58f and c6fb669.

Files selected for processing: 53

  • README.md
  • cmd/audit_1to1/internal/sdk/capture_declarations.go
  • cmd/audit_1to1/internal/sdk/captures_test.go
  • cmd/audit_1to1/internal/sdk/raw_reads.go
  • cmd/audit_1to1/internal/sdk/raw_reads_test.go
  • cmd/audit_1to1/internal/sdk/upstream_waits_test.go
  • docs/development/audit-1to1-baseline.json
  • docs/development/testing/testing.md
  • docs/development/token-footprint.md
  • internal/tools/action_specs_test.go
  • internal/tools/epics/epics.go
  • internal/tools/epics/epics_test.go
  • internal/tools/groupmilestones/group_milestones.go
  • internal/tools/groupmilestones/group_milestones_test.go
  • internal/tools/issuelinks/issue_links.go
  • internal/tools/issuelinks/issue_links_test.go
  • internal/tools/issuelinks/sent_shapes.go
  • internal/tools/issuelinks/shapes.go
  • internal/tools/issuelinks/shapes_test.go
  • internal/tools/issues/action_specs.go
  • internal/tools/issues/action_specs_test.go
  • internal/tools/issues/issues.go
  • internal/tools/issues/issues_test.go
  • internal/tools/issues/markdown.go
  • internal/tools/milestones/milestones.go
  • internal/tools/milestones/milestones_test.go
  • internal/tools/testdata/tools_individual.json
  • internal/tools/testdata/tools_meta.json
  • internal/tools/testdata/tools_meta_compact.json
  • internal/tools/testdata/tools_meta_full.json
  • internal/tools/todos/shapes.go
  • internal/tools/todos/todos.go
  • internal/tools/todos/todos_test.go
  • internal/tools/workitems/work_items.go
  • internal/tools/workitems/work_items_test.go
  • internal/toolutil/meta_tool.go
  • internal/toolutil/nullable.go
  • internal/toolutil/nullable_test.go
  • internal/toolutil/schema_type_overrides.go
  • internal/toolutil/sent_shapes.go
  • internal/toolutil/text.go
  • internal/toolutil/work_item_widgets.go
  • internal/toolutil/work_item_widgets_test.go
  • llms-full-individual-tools.txt
  • llms-full-meta-tools.txt
  • llms-full.txt
  • site/src/content/docs/es/reference/tools/group.mdx
  • site/src/content/docs/es/reference/tools/issue.mdx
  • site/src/content/docs/reference/tools/group.mdx
  • site/src/content/docs/reference/tools/issue.mdx
  • site/src/data/token-footprint.json
  • test/e2e/gitlab/ee/epics_test.go
  • test/e2e/gitlab/ee/issues_test.go
Files not fully reviewed because they exceed a per-file size limit: 2

  • llms-full-meta-tools.txt
  • llms-full.txt

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

@sonarqubecloud

Copy link
Copy Markdown

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

Labels

bug Something isn't working e2e The end-to-end suite and its Docker fixtures tooling The audit and generator commands under cmd/, and the Makefile targets that run them tools The GitLab tool surface. Issue-form area only: two thirds of pull requests touch it, so no path rule v3.5.0 Targeted at the 3.5.0 release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

issue.update drops the null its schema accepts for weight, so a weight cannot be removed

1 participant