instructure/canvas-lms · error · ArgumentError

submission not found in connection items

Error message

submission not found in connection items

What it means

PatchedArrayConnection overrides cursor computation for Submission nodes, matching items by submitted_at timestamp instead of AR id equality (submission history shares ids). If the given submission's submitted_at does not match any item in the connection's items array, it raises this ArgumentError — the node is not part of the page used to build cursors.

Solutions

  1. Only request cursors for nodes that came from this connection's items in the same request/page
  2. Re-fetch the connection so items and node are consistent (resubmission changes submitted_at)
  3. Verify the submission is part of the current page before computing the cursor
  4. If comparing history versions, ensure the exact serialized version (same submitted_at) is in items

Example fix

// before
const cursor = submissionConnection.cursorFor(someSubmission) // node from another page
// after
const item = submissionConnection.items.find(i => i.id === someSubmission.id)
const cursor = item ? submissionConnection.cursorFor(item) : null
Defensive patterns

Strategy: validation

Validate before calling

const inPage = connection.items.some(i => Math.floor(Date.parse(i.submittedAt) / 1000) === Math.floor(Date.parse(node.submittedAt) / 1000))
if (!inPage) throw new Error('submission not on this page; cannot compute cursor')

Try / catch

try {
  const cursor = connection.cursorFor(node)
} catch (e) {
  if (/submission not found in connection items/.test(e.message)) {
    await refetchConnection() // node/page drifted (e.g. resubmission)
  } else throw e
}

Prevention

When it happens

Trigger: Requesting a cursor (via cursor_for / pageInfo on a node) for a Submission whose submitted_at.to_i differs from every item in items — the submission belongs to another page of results, was resubmitted so its submitted_at changed after items were loaded, or the node was fabricated client-side.

Common situations: Paginated submission-history queries where a resubmission updates submitted_at between loading items and requesting the cursor; asking for a cursor of a submission outside the current page; custom loaders passing nodes that did not originate from this connection's items array.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/75953bb3ecdb4192. Report an issue: GitHub.

Appendix: source

Thrown at app/graphql/patched_array_connection.rb:31

# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

class PatchedArrayConnection < GraphQL::Pagination::ArrayConnection
  # The default ArrayConnection uses `find_index(item)` which uses `==` to get
  # the index. Unfortunately submission histories are saved through versionable
  # which returns an array of submission histories that all share the same id,
  # and active record overrides the `==` method to check for equality based on
  # said id. When dealing with submissions, change the comparator to look at the
  # submitted_at time (what submission#submission_history is doing) instead of
  # by id so cursors don't break.
  def cursor_for_submission_node(submission)
    submission_idx = items.find_index { |i| i.submitted_at.to_i == submission.submitted_at.to_i }
    raise ArgumentError, "submission not found in connection items" unless submission_idx

    encode((submission_idx + 1).to_s)
  end

  def cursor_for_quiz_submission_node(quiz_submission)
    # Use object identity (not AR `==` which compares by id) since all items
    # in the array share the same quiz_submission id but represent different
    # attempts via simply_versioned deserialization.
    quiz_submission_idx = items.find_index { |i| i.equal?(quiz_submission) }
    raise ArgumentError, "quiz_submission not found in connection items" unless quiz_submission_idx

    encode((quiz_submission_idx + 1).to_s)
  end

  def cursor_for(item)
    return cursor_for_submission_node(item) if item.instance_of?(Submission)
    return cursor_for_quiz_submission_node(item) if item.instance_of?(Quizzes::QuizSubmission)

View on GitHub (pinned to 1c9f0bb801)