Writing RBS

SkillDev tools

Use when writing, reviewing, or modifying RBS type signatures (sig/**/*.rbs, vendor/rbs/**/*.rbs, or inline #: annotations) or running Steep – e.g. "write rbs", "add type signatures", "type this class", "run steep", "check types". Enforces dd-trace-rb RBS conventions.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Writing RBS skill

What this skill tells your AI

The instructions your AI receives, as published by datadog/dd-trace-rb in .claude/skills/write-rbs/SKILL.md and read by ahel’s review.

Signatures live in sig/, mirroring lib/ one .rbs per .rb. Vendored gem stubs live in vendor/rbs/. ALWAYS check for an existing .rbs before writing a new one

Core rules

  • MUST leave a blank line between every signature
  • NEVER add a leading :: to a Ruby core class – write Hash, not ::Hash
  • MUST keep the :: when the enclosing namespace shadows a core or standard-library name – class Datadog::Core::Logger < ::Logger, else it inherits from itself
  • NEVER add a leading :: to the library's own modules; omit the namespace instead – write Internal::ClassName, not ::Datadog::Internal::ClassName
  • ALWAYS write a nilable as Type?, never Type | nil
  • NEVER copy Ruby comments into the .rbs
  • NEVER use untyped when the type is inferable from the code
  • NEVER loosen a signature just to satisfy Steep – a signature MUST harden the code's real contract, not paper over a checker error
  • Use any only when every possible type is intentionally valid and the code does not depend on the concrete type; use untyped when the type is merely not-yet-modeled, and be sparing with any
  • SHOULD reuse the signatures already present, and upgrade any untyped you can infer

Vendored signatures

  • NEVER write a vendor/rbs signature from how the calling code uses the gem – inference from usage can be wrong, and the caller may misuse the gem
  • ALWAYS read the gem's real source first; ask for its location if you lack it
  • SHOULD update stale or missing stubs, but only after seeing that source
  • NEVER use any in a vendored stub – any is a dd-trace-rb type alias, not a vendored concept; use untyped for genuinely open values there

Advanced techniques

Open the matching reference when a value calls for more than a plain type:

  • duck-typed – you call methods, not a concrete class → references/interfaces.md
  • a shape that repeats or carries a domain meaning → references/type-aliases.md
  • an output type that depends on the input type → references/generics.md
  • a callable → references/procs.md
  • a Class.new/Struct.new constant → references/class-definitions.md
  • correct Ruby the checker can't infer, or a one-off cast → references/steep-annotations.md
  • a file whose whole signature lives in the .rb (rbs-inline) → references/inline-rbs.md

Signature generation

bundle exec rake "rbs:prototype[lib/<path>.rb]"

Prototypes over-emit untyped and leading ::; clean the result against the rules above

Steep checks

While iterating, scope the check to the edited file's Ruby source – a sig/ path silently passes even on a broken signature:

bundle exec steep check lib/datadog/<path>.rb

This checks only that file; it will NOT catch breaks in signatures that reference it. ALWAYS run the full check before treating types as done:

bundle exec steep check

Signals

GitHub stars
416
Forks
417
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
write-rbs
Source
github.com/datadog/dd-trace-rb