#Debugging Payload.create Hang in CourseProgress Workflow

42 messages · Page 1 of 1 (latest)

cyan foxBOT
#

Hi! I'm seeing payload.create hang during a specific workflow:

  1. Participation afterChange hook calls initializeProgress server action.
  2. initializeProgress calls payload.create({ collection: 'courseProgress', data: ... }).
  3. CourseProgress has hooks:
// CourseProgress.ts hooks

hooks: {
  beforeChange: [
    async ({ data, operation }) => {
      console.log(`[CourseProgress.beforeChange] Start ${operation}`);
      // ... sync calculations on 'data' ...
      console.log(`[CourseProgress.beforeChange] Finish ${operation}`);
      return data;
    },
  ],
  afterChange: [
    async ({ doc, operation }) => {
      console.log(`[CourseProgress.afterChange] Start ${operation} ID: ${doc.id}`);
      // ... potentially triggers another hook async ...
      console.log(`[CourseProgress.afterChange] Finish ${operation} ID: ${doc.id}`);
      return doc;
    },
  ],
},

Problem: Logs show:

  1. initializeProgress: "Attempting payload.create for courseProgress..."
  2. CourseProgress.beforeChange: Logs "Start create" and "Finish create".
  3. --- HANG --- Execution stops here.

I don't see the log in initializeProgress after the await payload.create(...) line, nor the CourseProgress.afterChange logs immediately.

Why might payload.create hang after beforeChange completes but before afterChange starts? The beforeChange itself has no await calls.

Thanks!

grand aspenBOT
#

Original message from @barren adder - Moved from #general message

lyric wedge
#

Hey @barren adder

It would be more helpful to paste full code including your local api call - but I can tell right away that you are probably not passing a req to your local api call. You should try passing the req

#

This has to do with your local api call being excluded from the currently active transaction

barren adder
#

the beforeChange hooks finishs and gets stuck there
when i go to my Neon database and check active queries :
insert into "course_progress" ("id", "client_id", "course_id", "participation_id", "status", "percent_complete", "completed_lessons", "total_lessons", "course_type", "last_accessed_at", "completed_at", "passed_at", "certified_at", "exam_score", "updated_at", "created_at") values (default, $1, $2, $3, $4, $5, $6, $7, $8, default, default, default, default, default, $9, default) returning "id", "client_id", "course_id", "participation_id", "status", "percent_complete", "completed_lessons", "total_lessons", "course_type", "last_accessed_at", "completed_at", "passed_at", "certified_at", "exam_score", "updated_at", "created_at"

#

Then After a while it gets terminating Connection :
[error: terminating connection due to idle-in-transaction timeout]
when this happens it means CourseProgress got created

lyric wedge
#

Pass the req to the create call in your onProgress after change local api call

barren adder
#

you mean like this ? :
afterChange: [
// Run onProgressStatusChange in the background
async ({ doc, previousDoc, req, operation, collection, context }) => {
// Don't await the hook, let it run in background
onProgressStatusChange({
doc,
previousDoc,
req,
operation,
collection,
context,
}).catch((error: Error) => {
console.error('🔴 [CourseProgress] Error in background onProgressStatusChange:', error)
})
return doc
},
],

lyric wedge
#
onProgressStatusChange({
  doc,
  previousDoc,
  req,
  operation,
  collection,
  context,
})

You could be doing a few things, maybe even entering an infinite loop depending on what's inside this function

#

No

barren adder
#

i will give you that function

lyric wedge
#

Pass the req to the Local API that use inside this function

barren adder
lyric wedge
#

Wherever you use req.payload.<some_operation>() you need to pass the req there

#
await req.payload.update({
  collection: 'participation',
  id: participationId,
  data: {
    examCompleted: true,
    examScore: doc.examScore || 100, // Use the exam score if available
    examPassedAt: new Date().toISOString(),
    // progressSummary: {
    //   status: 'passed',
    //   percentComplete: doc.percentComplete || 100,
    //   lastAccessedAt: new Date().toISOString(),
    //   completedAt: doc.completedAt || new Date().toISOString(),
    // },
  },
  req, // notice here we pass req
})
barren adder
#

but this only happends if Course progress status Got updated to Passed

#

and when i create CourseProgress it gets created with Status of 'Not-Started'

lyric wedge
#

Right now all of your calls are being excluded from the transaction, so whatever operation is triggering this is not in the same context

#

You will get strange behavior if your workflow depends on things happening one after the other

barren adder
#

Exactly how im doing it

#

😅

lyric wedge
barren adder
#

Ok i will Try and pass the req and see if it solves it

#

Thank You very much for your time

lyric wedge
#

My pleasure, hope this solves your issue here

barren adder
#

I think i have circular hooks im trying to restructure my collections and server actions

lyric wedge
#

I see, yeah, you always have to be careful running operations in an afterChange hook as it can cause looping logic that triggers itself

barren adder
#

This is the action that create a participation then whe the participation is created it creates a CourseProgress for it then the CourseProgress goes and update the Participation to write courseprogressID in the participation

lyric wedge
#

Need to scope some stuff

barren adder
#

so i think docs gets checked before they get updated and never stops

#

TBH idk if my conclusion is correct or not , im new to payload and im junior

barren adder
lyric wedge
#

You can also scope things based on the data in your doc adn the oerpation being used passed to your hook directly

#

if (operation === 'create' && !doc.title) { ... }

barren adder
lyric wedge
#

An afterChange hook receives certain props passed to it. For example, the data of the doc, the operation that the hook is running against (create, update, etc,), and other props. All I'm saying is that before doing a payload local api operation, such as update or create or find, etc, you can perform an if check to see if the operation should run

barren adder
#

Ah i see

#

True that sounds better im doing the check inside the hook