Writing RBS
SkillDev toolsUse 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.
No other account needed.
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 – writeHash, 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 – writeInternal::ClassName, not::Datadog::Internal::ClassName - ALWAYS write a nilable as
Type?, neverType | nil - NEVER copy Ruby comments into the
.rbs - NEVER use
untypedwhen 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
anyonly when every possible type is intentionally valid and the code does not depend on the concrete type; useuntypedwhen the type is merely not-yet-modeled, and be sparing withany - SHOULD reuse the signatures already present, and upgrade any
untypedyou can infer
Vendored signatures
- NEVER write a
vendor/rbssignature 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
anyin a vendored stub –anyis a dd-trace-rb type alias, not a vendored concept; useuntypedfor 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.newconstant →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