DEV Community

Akanksha Trehun
Akanksha Trehun

Posted on

Week 2: Pull Requests, Rejected Code, and the Art of Not Breaking Things

GSoC 2026 | CircuitVerse × Canvas LMS LTI 1.3 Integration


If Week 1 was about getting familiar with the codebase and understanding what needed to be built, Week 2 was about learning the hard way that writing code is only half the job. The other half — the messier, more humbling half — is getting that code accepted by the people who actually maintain the project.

This week was full of detours, rejected pull requests, reviewer feedback that stung a little, and a surprisingly frustrating fight with a two-letter word in Ruby. But by the end of it, I had something real to show: a clean, reviewed, and submitted change to CircuitVerse that lays the foundation for the entire LTI 1.3 integration. Let me walk you through it.


A Quick Refresher: What Are We Building?

CircuitVerse is an open-source platform where students can build and simulate digital circuits right in their browser. The project I'm working on aims to connect CircuitVerse with Canvas, one of the most widely used Learning Management Systems (LMS) in universities around the world.

The technology that makes this connection possible is called LTI — Learning Tools Interoperability. Think of it as a universal plug that lets any educational tool (like CircuitVerse) slot into any LMS (like Canvas) so that students can log in once, get assignments, submit work, and have their grades flow back automatically — all without leaving their course page.

There are two versions of this plug: LTI 1.1, which is old and uses a simpler (but outdated) security mechanism, and LTI 1.3, which is newer, more secure, and what Canvas actually recommends today. My job is to bring CircuitVerse fully up to LTI 1.3 standards.


Monday–Tuesday: A Pull Request That Taught Me to Read Diffs

I started the week with what I thought was a solid pull request (PR) — a fix for a bug in CircuitVerse's existing LTI 1.1 grade passback feature. "Grade passback" is the process where CircuitVerse sends a student's score back to Canvas after they complete an assignment.

My PR got reviewed, and the reviewer left two comments that immediately made me wince:

Comment 1: "It seems that the whole schema is sorted. This issue has been fixed upstream."

Translation: I had accidentally included a massive, unrelated change to a file called schema.rb — a file that describes the structure of CircuitVerse's database. When I ran a command to regenerate this file on my laptop, a newer version of the Rails framework (the toolkit CircuitVerse is built on) quietly reordered every single database column alphabetically. This produced a diff with hundreds of changed lines — none of which were related to my actual fix. The reviewer spotted it immediately.

Comment 2: "You are downgrading gem version here."

Translation: I had accidentally rolled back a security upgrade. CircuitVerse uses a library called jwt (JSON Web Tokens — a way to securely verify identity online) that had recently been upgraded from version 2.x to 3.x. My branch had unknowingly reverted it to the older version because of a chain reaction: another library I'd updated (webpush, used for browser notifications) happened to be incompatible with jwt 3.x, pulling the version back down without me realising.

The Fix: I had to undo the schema changes by restoring the file to exactly what the main CircuitVerse codebase had, restore jwt to version 3.2.0, and also downgrade webpush back to the version that didn't cause the conflict. Three files. Careful surgery with git commands. Then push the corrected version, re-commit, and update the PR.

This taught me something I'll carry for the rest of the summer: always check your diff before opening a pull request. A diff is the list of everything your code changes compared to the original. If anything looks unfamiliar or unexpected, it probably shouldn't be there.


Wednesday: The PR I Had to Close

After fixing those issues and updating the pull request, I got some harder news: the PR had to be closed. The grade passback code I'd written depended on an old library (ims-lti) that used LTI 1.1's way of doing things — OAuth 1.0, an older form of authentication. The problem was that several other files in the project also depended on that same library, and changing it was causing things to break in places I hadn't touched.

It felt frustrating. But my mentor pointed me in the right direction: before fixing the grade passback flow, I needed to first upgrade the underlying library itself. That meant replacing ims-lti with something modern that could handle LTI 1.3 properly.


Wednesday–Thursday: Reviewing Someone Else's Pull Request

While I regrouped, I was asked to review another PR that had been submitted — a Dependabot update (an automated tool that suggests library upgrades) proposing to bump ims-lti from version 1.2.9 to 2.3.7.

This was a useful exercise. Looking at what changed between the two versions, I spotted that:

  • The 2.x version had removed support for the OAuth library entirely — the same one that LtiScoreSubmission (the grade passback code) depended on
  • A class called IMS::LTI::ToolProvider, which is what the controller uses to verify a student's identity when they launch CircuitVerse from Canvas, simply doesn't exist in 2.x

Merging this PR would have silently broken every LTI launch and every grade submission. I wrote up a review explaining this and recommended the PR be held until the LTI code was properly migrated to 1.3. The PR was not merged.

Reviewing other people's code with a critical eye is a skill in itself, and this was good practice.


Thursday: Syncing With the Main Codebase (A Surprisingly Tricky Task)

Before writing any new code, I needed to make sure my local copy of CircuitVerse was up to date with the changes the main team had been making. This process is called "fetching upstream" — pulling in changes from the official CircuitVerse repository.

It should have been simple. It wasn't.

Git (the tool developers use to track code changes) threw an error about corrupted branch references — essentially, some branch names in the CircuitVerse repository contained spaces in their names, which git wasn't expecting. These bad references were blocking the entire fetch operation.

The fix involved tracking down these broken references, deleting them, and then fetching again. Once that worked, I had a new problem: a merge conflict. The main codebase had changed the jwt library version (to 3.2.0), while my local copy still had the older pin (2.10.3). Git didn't know which one to keep, so it stopped and asked me to decide.

I chose the upstream version (3.2.0) — the right call, since that's the one the maintainers had intentionally upgraded to. I also removed a manually-added entry for a library called faraday (used for making HTTP requests) since it was already included indirectly through other libraries and didn't need to be declared explicitly.

There was also a leftover reference to a submodule (a separate mini-repository embedded inside CircuitVerse) that I'd already removed in an earlier session. The merge picked it back up; I removed it again. After all of that, the sync was complete.


Friday: Finding the Right Gem — A Detective Story

With the codebase up to date, it was time to research the actual replacement for ims-lti. The library I'd been asked to migrate to is called lti_advantage.

Here's where it got interesting. There are two different things with very similar names:

  1. lti_advantage (version 0.0.3) — published on RubyGems (the standard place Ruby libraries are shared), authored by an individual developer, with no publicly accessible source code and almost no documentation.

  2. lti-advantage (version 0.1.0) — embedded directly inside Canvas LMS's own codebase, maintained by Instructure (the company that builds Canvas), with full source code, tests, and comments linking to the official LTI standards.

Since the entire point of this integration is to work with Canvas, using the library that Canvas itself uses was clearly the right call. The Canvas version is battle-tested, actively maintained, and built to the exact same LTI 1.3 specification we're implementing.

I updated the Gemfile (the file that lists all libraries a Ruby application depends on) to point to Canvas's version of the gem, directly from the Canvas GitHub repository.


Friday Evening: The Two-Letter Word That Cost Me an Hour

After setting up the Gemfile, I ran a quick test to verify the library had loaded correctly. Instead of a confirmation, I got a wall of red error text.

The culprit? The word it.

In Ruby 3.4 (released late 2023), it was introduced as a new shorthand inside code blocks — a way to refer to the current item being processed without naming it explicitly. The Canvas team had adopted this syntax in their codebase.

But CircuitVerse runs on Ruby 3.2.2. In that version, it isn't a special keyword — it's just an undefined method name. So when the library tried to load, Ruby 3.2.2 hit the word it and said: "I have no idea what this is."

The solution was to find the specific point in Canvas's git history just before this syntax was introduced — a commit from March 2025, before the Canvas team upgraded their Rubocop linting tool to support Ruby 3.4 style. By pinning our Gemfile to that specific historical commit, we get the full functionality of the library without the Ruby version incompatibility.

After making that change, the verification check finally printed what I needed to see:

0.1.0
LtiAdvantage::Messages::ResourceLinkRequest
Enter fullscreen mode Exit fullscreen mode

The library was in, working, and ready.


Wrapping Up: 793 Tests, 0 Failures

With the new library loaded, the last step was making sure I hadn't accidentally broken anything else in the process. I ran CircuitVerse's full test suite — 793 individual automated tests covering models, controllers, API endpoints, background jobs, and more.

793 examples, 0 failures.

Every part of the existing application still worked exactly as before. The only test excluded from this run was the one specifically for LTI — which I already knew would fail, because it still references the old library. Rewriting that test is part of next week's work.

I created a clean pull request with a single, focused commit describing exactly what changed and why, linked it to the issue (#7509) I'd opened for the work, and submitted it.


What I Learned This Week

  • Read your diffs. Every line in a pull request should be there intentionally. Unexpected changes are usually a sign something went wrong.
  • Library upgrades are never just library upgrades. Every dependency has dependencies of its own, and changing one can quietly shift others.
  • Reviewing code is as valuable as writing it. Catching a breaking change before it merges is just as important as shipping a feature.
  • Version numbers matter — down to the commit. Pinning a library to a specific historical commit felt overly cautious at first, but it was the only correct solution given the Ruby version gap.
  • A clean pull request with one clear purpose is worth far more than a large one with many. My first PR of the week was rejected partly because it accidentally carried in unrelated changes. The PR at the end of the week had exactly one commit, one purpose, and zero noise.

Next week: I start the actual code migration — replacing the old LTI 1.1 authentication logic with the new lti-advantage library's APIs, rewriting the tests, and wiring up the full OIDC launch flow that Canvas LTI 1.3 requires.

The foundation is set. Time to build.

Top comments (0)