Skip to content

doc: add a Claude Code rule for the C/Ruby boundary - #6998

Closed
takumin wants to merge 2 commits into
mruby:masterfrom
takumin:c-ruby-boundary-rule
Closed

doc: add a Claude Code rule for the C/Ruby boundary#6998
takumin wants to merge 2 commits into
mruby:masterfrom
takumin:c-ruby-boundary-rule

Conversation

@takumin

@takumin takumin commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

This adds one Markdown file under .claude/, describing where the boundary
between C and Ruby lies in this tree. It documents an existing convention for
coding agents. It does not propose a new one, and it does not change any rule
that applies to human contributors.

CONTRIBUTING.md documents the coding conventions that a patch has to satisfy:
C99 conformance, keeping library dependencies minimal, the newline after a
return type, and ISO/IEC 30170:2012 conformance for Ruby code. It says nothing
about where the boundary between C and Ruby lies, in particular about when a C
function may call back into the VM. That convention exists, but it is currently
learned only through review.

Background

In #6994 I added Regexp.__match_pattern in C so that String#match could
reject an argument that merely pretends to be a Regexp. The check ran in C,
but the accepted String was then compiled with
mrb_funcall_argv1(Regexp, new, str) in the same function.

In fd5e075 matz split it: the type check stays in C, and the Regexp.new
construction moves to mrblib, because Module#=== cannot be redefined either,
so the caller can compile the pattern without weakening the check.

The reasoning generalizes well beyond that one function, and I had no way to
find it stated anywhere in the tree. This patch writes it down.

What this adds

A single documentation file, .claude/rules/mruby-c-ruby-boundary.md, stating:

  • Keep type checks, argument validation, internal representation access, and
    low-level primitives inside C.
  • Do not re-enter the VM from a helper whose purpose is validation or
    normalization. The test is not whether the operation is a conversion, but
    whether it involves method dispatch.
  • The default split of responsibility between the C side and the Ruby side.
  • The cases where entering the VM from C is legitimate, together with the
    existing code that relies on them, so that they are not mistaken for
    violations. This covers convert_type and the to_str protocol in
    src/object.c, mrb_obj_new and the inherited / included /
    method_missing hooks in src/class.c, the default proc in src/hash.c,
    and the to_a / to_enum delegations in src/array.c.
  • A review checklist for changes that do enter the VM from C, covering
    re-entrancy, exception propagation, non-local exits, GC rooting, and
    evaluation order.

Every exemption listed is existing mruby code that stays exactly as it is.

Note on the location

This introduces a .claude/ directory, which the repository does not have
today. The path is what Claude Code loads automatically for files matching the
paths: list in the front matter, so an agent editing src/*.c or
mrbgems/*/src/*.c picks the convention up without being told.

If you would rather not carry a tool-specific directory, I am happy to move the
content into CONTRIBUTING.md or doc/, or to drop the front matter entirely.
The text itself is plain Markdown and does not depend on the location. Please
say which you prefer.

Impact

Documentation only. Nothing here runs in CI, and no workflow, source, build, or
test file is touched. The file is formatted with prettier@3.7.4, matching the
manual prettier hook in .pre-commit-config.yaml.

Summary by CodeRabbit

  • Documentation
    • Added development guidance clarifying responsibilities between C and Ruby code.
    • Documented validation, conversion, dispatch, exception handling, garbage collection, compatibility, and testing considerations.
    • Added review questions to support consistent implementation decisions.

`CONTRIBUTING.md` covers C99 conformance, minimal library dependencies, the
newline after a return type, and ISO/IEC 30170:2012 conformance for Ruby code,
but it says nothing about when a C function may re-enter the VM.

The convention surfaced in fd5e075. `Regexp.__match_pattern`, added in mruby#6994,
compiled an accepted `String` with `mrb_funcall_argv1(Regexp, new, str)`, and
matz split it so that the type check stays in C while the `Regexp.new`
construction moves to mrblib. This file states that boundary along with the
existing exemptions, such as the `to_str` conversion protocol and the
`initialize` call in `mrb_obj_new`, so that coding agents working in this tree
do not have to rediscover it.
@takumin
takumin requested a review from matz as a code owner August 3, 2026 01:33
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fb474a13-0797-4a01-a46e-dcaa9afa5beb

📥 Commits

Reviewing files that changed from the base of the PR and between e64b62a and 9dd379d.

📒 Files selected for processing (1)
  • .claude/rules/mruby-c-ruby-boundary.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • .claude/rules/mruby-c-ruby-boundary.md

📝 Walkthrough

Walkthrough

Added a repository rule that defines the C/Ruby boundary in mruby. It assigns low-level operations to C, dispatching behavior to Ruby, and documents VM-entry exemptions and review requirements.

Changes

C/Ruby boundary guidance

Layer / File(s) Summary
Boundary contract
.claude/rules/mruby-c-ruby-boundary.md
Defines C responsibilities for validation, type inspection, representation access, low-level operations, and non-dispatching conversions. Defines Ruby responsibilities for dispatch, object construction, overridable behavior, and high-level control flow.
VM entry exemptions and review
.claude/rules/mruby-c-ruby-boundary.md
Documents permitted VM-entry cases and review checks for re-entrancy, exceptions, control flow, GC rooting, compatibility, and tests.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Suggested reviewers: matz

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the documentation-only change that adds a Claude Code rule for the C/Ruby boundary.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
.claude/rules/mruby-c-ruby-boundary.md (1)

85-98: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Document the intentional hash VM entry.

mrb_obj_hash_code in src/hash.c:339-382 calls mrb_funcall_argv for non-primitive keys so user-defined hash methods work. This call is not listed under the existing exemptions. Add it, or state that operations whose Ruby semantics require overridable hash dispatch are exempt.

Without this clarification, future reviews may apply the prohibition too broadly and remove required Ruby dispatch.

Proposed clarification
 ### Existing exempt cases
+- Object hashing in `mrb_obj_hash_code` in `src/hash.c`, where dispatch to
+  `hash` is required to preserve Ruby's overridable hashing semantics.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/rules/mruby-c-ruby-boundary.md around lines 85 - 98, Update the
“Existing exempt cases” section to explicitly exempt mrb_obj_hash_code in
src/hash.c, including its intentional mrb_funcall_argv dispatch for
non-primitive keys, or state the equivalent rule that Ruby operations requiring
overridable hash dispatch are exempt. Preserve the prohibition for unrelated
C-to-Ruby calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/rules/mruby-c-ruby-boundary.md:
- Around line 2-7: The path patterns in the rule’s paths configuration do not
cover nested gem sources or gem-local headers. Replace the non-recursive mrbgems
src and mrblib patterns with recursive equivalents, and add a recursive mrbgems
include header pattern so all gem C, header, and Ruby files are matched.

---

Nitpick comments:
In @.claude/rules/mruby-c-ruby-boundary.md:
- Around line 85-98: Update the “Existing exempt cases” section to explicitly
exempt mrb_obj_hash_code in src/hash.c, including its intentional
mrb_funcall_argv dispatch for non-primitive keys, or state the equivalent rule
that Ruby operations requiring overridable hash dispatch are exempt. Preserve
the prohibition for unrelated C-to-Ruby calls.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cc9d74b8-21ec-4ae2-86a1-1811d800774b

📥 Commits

Reviewing files that changed from the base of the PR and between 2fdad90 and e64b62a.

📒 Files selected for processing (1)
  • .claude/rules/mruby-c-ruby-boundary.md

Comment thread .claude/rules/mruby-c-ruby-boundary.md
The rule already applied to `include/**/*.h` at the top level but not to
gem-local headers such as `mrbgems/mruby-regexp/include/re_internal.h`,
and it missed the C sources under `mrbgems/*/tools/`, which implement
`mirb` and `mrdb`.

The gem `src` and `mrblib` patterns stay non-recursive on purpose. The
only nested sources under a gem `src` directory are the vendored prism
files in `mrbgems/mruby-compiler/lib/prism/src/`, and no gem has a nested
`mrblib` directory at all.
@matz

matz commented Aug 3, 2026

Copy link
Copy Markdown
Member

Thank you for writing this up. You found a real gap, and I want to be clear that the substance is right: the convention exists, it is not written down anywhere in the tree, and you had no way to discover it before writing the code in #6994. I checked every exemption you listed against the source and they are all accurate.

I am going to decline the location and take the content.

Why not .claude/

I have deliberately kept CLAUDE.md and .claude/ out of this repository. They are in my local .git/info/exclude, which is a per-clone file that is never committed, so their absence is a choice rather than an oversight.

How people use AI tools is their own business, and I do not want mruby to take a position on it. The repository has no AGENTS.md, no .github/copilot-instructions.md, no .cursor/. If I accept a directory for one tool I have no principled reason to refuse the next one, and contributors using anything else get nothing from the file. The paths: front matter is read by exactly one program; it means nothing to a human or to any other tool. A file that most contributors never open is also a file that stops being updated when the convention shifts.

None of that is an argument against what you wrote.

Where it went instead

The rule belongs beside the other C conventions, so I put a condensed version in CONTRIBUTING.md under Coding conventions -> C code, next to C99 conformance and keeping library dependencies minimal. It carries the same weight as those, and it now reaches every contributor. It also reaches every coding agent, since they all read CONTRIBUTING.md when pointed at it, so you lose nothing on that front.

What I kept:

  • the basic principle, that a function whose job is validation, type determination, normalization, or reading an internal representation does that work in C and stops there
  • the reasons, which I stated explicitly: re-entrant execution can move the stack and invalidate pointers held across the call, and a redefined method can change the outcome of a check meant to be authoritative
  • the split, with String#match as the worked example, plus the __-prefixed fast path called from a wrapping Ruby method
  • that the test is the responsibility of the function, not the presence of mrb_funcall*()
  • the legitimate cases: convert_type() for the to_str protocol, mrb_obj_new() and the class hooks, the default proc in src/hash.c, the to_a and to_enum delegations in src/array.c
  • the obligation to say why in a comment or in the pull request, and to check exception and break propagation, survival of intermediate state across re-entry, and GC rooting

What I dropped: the eight-point review checklist and the two-column responsibility table. They are good, but CONTRIBUTING.md sections here run about ten lines each and I did not want this one to dwarf the rest. If the detail turns out to be wanted, doc/internal/ is the place for it and I would look at a patch adding it there.

So: closing this one, with the content merged. Thank you for the write-up, and for the honest framing in #6994 that led to it.

@matz matz closed this Aug 3, 2026
@takumin
takumin deleted the c-ruby-boundary-rule branch August 3, 2026 05:14
@takumin

takumin commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Thank you for the detailed explanation and for incorporating the substance into CONTRIBUTING.md.

I had not realized that the absence of tool-specific files was a deliberate repository policy. The distinction makes sense: a project-wide convention should live in the canonical contributor documentation, where human contributors and coding agents can use the same source of truth.

The condensed section preserves the important boundary, its rationale, the String#match example, the legitimate VM-entry cases, and the review obligations. I also appreciate your explicitly recognizing that this convention was not discoverable when I wrote #6994.

The note about doc/internal/ is helpful. I do not think a follow-up is necessary now; the CONTRIBUTING.md version is sufficient, and the longer checklist can wait until there is a concrete need for it.

Thank you again for reviewing the details and preserving the substance.

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