Skip to content

fix: make runName optional#48

Open
gullmar wants to merge 2 commits into
dev/0.9.0from
fix/optional-run-name
Open

fix: make runName optional#48
gullmar wants to merge 2 commits into
dev/0.9.0from
fix/optional-run-name

Conversation

@gullmar

@gullmar gullmar commented May 11, 2026

Copy link
Copy Markdown
Collaborator
  • Fix Actor client's start and call signature by moving runName inside options, making it coherent with the Task client.
  • Introduce the concept of requestId, which identifies a specific request, and corresponds to the runName, if provided.
  • Generate a requestId when runName is not provided, hashing the resource ID, input, and options.

Addresses #35.

Updates Actor and Task client APIs so runName is optional and supplied through options, aligning their start and call signatures. Introduces deterministic requestId generation and propagates request IDs through scheduling, tracking, run clients, batching, and error handling to reliably deduplicate and identify runs. Adds stable hashing support and updates tests and end-to-end dependencies accordingly.

@gullmar gullmar self-assigned this May 11, 2026
@gullmar
gullmar force-pushed the fix/optional-run-name branch 3 times, most recently from fa25212 to 2b09b54 Compare May 12, 2026 06:48
@gullmar
gullmar force-pushed the fix/optional-run-name branch from a74d355 to 84e6a60 Compare June 6, 2026 15:25
@gullmar
gullmar changed the base branch from main to dev/0.9.0 June 6, 2026 15:26
@gullmar
gullmar force-pushed the fix/optional-run-name branch from 84e6a60 to e366e0e Compare July 13, 2026 17:58
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change introduces deterministic request IDs derived from run sources, input, options, and optional run names. Scheduler, context, tracker, actor, task, Apify, and run clients now propagate request IDs through extended run objects and batch records. Public start and call contracts move run names into options. Tests, mocks, E2E calls, hashing utilities, and actor-local dependency installation are updated accordingly.

Poem

Names drift away, IDs take flight,
Hashes hum softly through the night.
Runs start, wait, and track their thread,
Extended fields adorn each head.
Batches bloom in ordered flow—
A tidy trail for runs to go.

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
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.

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: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/run-scheduler.ts (1)

90-104: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Keep the request ID derived from the adapted request.
requestRunStart()/startRun() key the pool before adaptation, but processRunRequest() stores the run under the adapted request ID. When the adapter changes the hash (for example, unnamed runs once fixedInput is merged), lookup and dedupe can diverge. Compute the key once from the adapted request, or carry the original key through tracking.

🤖 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 `@src/run-scheduler.ts` around lines 90 - 104, The request pool key must match
the ID used by processRunRequest after adaptation. Update requestRunStart and
startRun to adapt the RunStartRequest before calling getRequestId and
pool.findOrAddRequest, computing the adapted ID once and reusing it for
lookup/deduplication while preserving the adapted request for processing.
🤖 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 `@package.json`:
- Around line 58-59: Update the package version in package.json from 0.9.0-rc.1
to the appropriate bumped release version for the request identity and public
client contract changes before publishing.

In `@src/__unit__/mocks.ts`:
- Around line 8-9: Update createActorRunMock to construct a complete valid
ExtendedActorRun with defaults for required fields such as requestId, id, and
status, then spread params over those defaults. Remove the direct cast of
arbitrary partial data so overrides remain type-safe and the mock always
satisfies the ExtendedActorRun contract.

In `@src/clients/actor-client.ts`:
- Around line 103-107: Strip the call-only waitSecs option before invoking start
in the ActorClient call flow, while continuing to pass it to waitForFinish.
Apply the same change to src/clients/actor-client.ts lines 103-107 and
src/clients/task-client.ts lines 90-93 so waitSecs does not affect request-id
hashing or Run creation; preserve all other options.

In `@src/clients/task-client.test.ts`:
- Around line 110-124: Update the relevant task-client tests, including the
“generates a request ID if no run name is provided” case and the additional
indicated assertions, to include the expected requestId in each returned-result
objectContaining check. Use the mock run’s request identifier so the tests
verify requestId propagation alongside id and status.

In `@src/clients/task-client.ts`:
- Line 48: Update the warning in the duplicate-enqueue branch of the task client
to describe duplicate requests, including unnamed identical requests, rather
than only duplicate run names. Reuse the Actor client wording for “identical Run
requests, or requests with the same name,” while preserving the existing
requestId-prefixed logger and readable formatting.

In `@src/entities/run-source.ts`:
- Around line 35-38: Add unit tests for RunSource.getRequestId covering the
runName override and verifying generated IDs change when source ID, input, or
options differ. Use the existing RunSource test setup and assert stable
request-ID behavior without altering the implementation.

In `@src/types.ts`:
- Around line 508-511: Update the JSDoc for exported RunRecord in src/types.ts
lines 508-511 to state that records are keyed by request ID rather than run
name. Also update the final comment in src/context/client-context.ts lines 52-62
to refer to a request ID; no code behavior changes are needed.

In `@src/utils/hash.ts`:
- Around line 3-7: Update stringifyObject to recursively canonicalize object
keys without using the top-level key list as JSON.stringify’s global whitelist,
preserving every nested field. In src/utils/hash.ts lines 3-7, change
stringifyObject accordingly; in src/utils/hash.test.ts lines 17-20, add a case
where objects differing only in a nested value produce different hashes.
- Around line 10-12: Update hashObject to serialize objects with a recursive,
stable stringify that preserves nested properties and deterministic key ordering
before passing the result to murmur3. Replace or fix stringifyObject so nested
structures cannot collapse into the same hash, while preserving the existing
hexadecimal hash output.

---

Outside diff comments:
In `@src/run-scheduler.ts`:
- Around line 90-104: The request pool key must match the ID used by
processRunRequest after adaptation. Update requestRunStart and startRun to adapt
the RunStartRequest before calling getRequestId and pool.findOrAddRequest,
computing the adapted ID once and reusing it for lookup/deduplication while
preserving the adapted request for processing.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 617d743b-f737-4355-8527-a19d5330d283

📥 Commits

Reviewing files that changed from the base of the PR and between 1632a6a and e366e0e.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json, !package-lock.json
  • src/utils/__snapshots__/hash.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (25)
  • e2e-test.js
  • package.json
  • src/__e2e__/resurrection.ts
  • src/__e2e__/test-actor-runner.ts
  • src/__unit__/mocks.ts
  • src/clients/actor-client.test.ts
  • src/clients/actor-client.ts
  • src/clients/apify-client.test.ts
  • src/clients/apify-client.ts
  • src/clients/run-client.test.ts
  • src/clients/run-client.ts
  • src/clients/task-client.test.ts
  • src/clients/task-client.ts
  • src/context/client-context.test.ts
  • src/context/client-context.ts
  • src/entities/run-source.test.ts
  • src/entities/run-source.ts
  • src/errors.ts
  • src/run-scheduler.test.ts
  • src/run-scheduler.ts
  • src/run-tracker.test.ts
  • src/run-tracker.ts
  • src/types.ts
  • src/utils/hash.test.ts
  • src/utils/hash.ts

Comment thread package.json
Comment on lines +58 to +59
"apify-client": "^2.22.3",
"murmurhash-js": "^1.0.0"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Bump the package version for this API change.

The package remains 0.9.0-rc.1 while request identity and public client contracts change. Update the appropriate release version before publishing. As per coding guidelines, “Remember to bump the patch/minor/major version number.”

🤖 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 `@package.json` around lines 58 - 59, Update the package version in
package.json from 0.9.0-rc.1 to the appropriate bumped release version for the
request identity and public client contract changes before publishing.

Source: Coding guidelines

Comment thread src/__unit__/mocks.ts
Comment on lines +8 to +9
export function createActorRunMock(params: Partial<ExtendedActorRun> = {}): ExtendedActorRun {
return { ...params } as ExtendedActorRun;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Keep the mock valid instead of casting arbitrary partial data.

This cast allows {} to masquerade as an ExtendedActorRun, hiding missing requestId, id, or status fields in tests. Restore valid defaults and only spread overrides onto them.

Proposed fix
 export function createActorRunMock(params: Partial<ExtendedActorRun> = {}): ExtendedActorRun {
-    return { ...params } as ExtendedActorRun;
+    return {
+        id: 'mock-run-id',
+        requestId: 'mock-request-id',
+        status: 'RUNNING',
+        startedAt: new Date('2024-01-01T00:00:00.000Z'),
+        ...params,
+    } as ExtendedActorRun;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function createActorRunMock(params: Partial<ExtendedActorRun> = {}): ExtendedActorRun {
return { ...params } as ExtendedActorRun;
export function createActorRunMock(params: Partial<ExtendedActorRun> = {}): ExtendedActorRun {
return {
id: 'mock-run-id',
requestId: 'mock-request-id',
status: 'RUNNING',
startedAt: new Date('2024-01-01T00:00:00.000Z'),
...params,
} as ExtendedActorRun;
}
🤖 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 `@src/__unit__/mocks.ts` around lines 8 - 9, Update createActorRunMock to
construct a complete valid ExtendedActorRun with defaults for required fields
such as requestId, id, and status, then spread params over those defaults.
Remove the direct cast of arbitrary partial data so overrides remain type-safe
and the mock always satisfies the ExtendedActorRun contract.

Comment on lines +103 to 107
override async call(input?: object, options?: ExtendedActorCallOptions): Promise<ExtendedActorRun> {
const startedRun = await this.start(input, options);
return this.apifyClient
.extendedRunClient(runName, startedRun.id)
.extendedRunClient(startedRun.requestId, startedRun.id)
.waitForFinish({ waitSecs: options?.waitSecs });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== file list ==\n'
git ls-files 'src/clients/*' 'src/*types*' | sed -n '1,120p'

printf '\n== outlines ==\n'
ast-grep outline src/clients/actor-client.ts --view expanded || true
ast-grep outline src/clients/task-client.ts --view expanded || true

printf '\n== actor-client relevant slice ==\n'
sed -n '1,180p' src/clients/actor-client.ts

printf '\n== task-client relevant slice ==\n'
sed -n '1,170p' src/clients/task-client.ts

printf '\n== call option types / start option types ==\n'
rg -n "waitSecs|ExtendedActorCallOptions|ExtendedTaskCallOptions|start\\(" src -g '*.ts'

Repository: apify-professional-services/apify-orchestrator

Length of output: 17704


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== types around call/start options ==\n'
sed -n '170,340p' src/types.ts

printf '\n== RunSource and request-id logic ==\n'
sed -n '1,240p' src/entities/run-source.ts

printf '\n== apify-client wrapper methods ==\n'
sed -n '1,240p' src/clients/apify-client.ts

printf '\n== any tests around waitSecs or duplicate runs ==\n'
rg -n "waitSecs|duplicate|requestId|runName" src/clients/*.test.ts src/entities/*.test.ts src -g '*.ts'

Repository: apify-professional-services/apify-orchestrator

Length of output: 50402


🌐 Web query:

Apify Client JS ActorCallOptions waitSecs TaskCallOptions waitSecs start options requestId hashing

💡 Result:

In the Apify Client for JavaScript, waitSecs is a property available in ActorCallOptions and TaskCallOptions that defines the maximum duration in seconds to wait for an actor or task run to finish [1][2][3][4]. When using the call method (e.g., client.actor('actor-id').call), the client starts the run and automatically polls for its completion [4]. If the run finishes within the specified waitSecs, the call method resolves with the run object; if the time limit is exceeded, the wait concludes [3][4]. Regarding request ID hashing or related options, there is no direct reference in the standard ActorCallOptions or TaskCallOptions for "hashing" options in the Apify Client JS documentation [2][3][5]. The available options for starting and calling actors/tasks primarily focus on runtime configuration such as build tags, memory limits, timeouts, and webhooks [3][5]. If you are looking for mechanisms to ensure request idempotency or unique identification, Apify's API typically handles this through the platform's internal run management, and users typically manage uniqueness via input parameters rather than client-side hashing options [3][4]. Key differences between options: - waitSecs (used in call): Implemented via client-side polling; wait time is indefinite by default unless specified [3][4]. - waitForFinish (used in start): A parameter that executes on the API side directly, limited to 60 seconds [5]. Top results: [1] https://docs.apify.com/api/client/js/reference/interface/ActorCallOptions [3] https://github.com/apify/apify-client-js/blob/master/src/resource_clients/actor.ts [4] https://docs.apify.com/api/client/js/reference/class/ActorClient [5] https://docs.apify.com/api/client/js/reference/interface/ActorStartOptions.md

Citations:


Strip waitSecs before starting the Run
waitSecs is call-only, but both src/clients/actor-client.ts and src/clients/task-client.ts pass it into start, so it changes the request-id hash. Identical calls with different wait times can create duplicate Runs.

📍 Affects 2 files
  • src/clients/actor-client.ts#L103-L107 (this comment)
  • src/clients/task-client.ts#L90-L93
🤖 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 `@src/clients/actor-client.ts` around lines 103 - 107, Strip the call-only
waitSecs option before invoking start in the ActorClient call flow, while
continuing to pass it to waitForFinish. Apply the same change to
src/clients/actor-client.ts lines 103-107 and src/clients/task-client.ts lines
90-93 so waitSecs does not affect request-id hashing or Run creation; preserve
all other options.

Comment on lines +110 to +124
expect(result).toStrictEqual(expect.objectContaining({ id: mockRun.id, status: mockRun.status }));
});
});

describe('call', () => {
it('throws if called without a run name', async () => {
await expect(taskClient.call({ key: 'value1' })).rejects.toThrow();
it('generates a request ID if no run name is provided', async () => {
const result = await taskClient.start({ key: 'value1' });

expect(apifyClient.findOrStartRun).toHaveBeenCalledWith(
expect.objectContaining({
source: runSource,
runName: undefined,
input: { key: 'value1' },
options: undefined,
}),
);
expect(result).toStrictEqual(expect.objectContaining({ id: mockRun.id, status: mockRun.status }));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Assert the returned requestId.

These assertions only check id and status, so request-identifier propagation can regress unnoticed—including in the test named “generates a request ID.” Include the expected requestId in each objectContaining.

As per coding guidelines, “Remember to add/fix unit tests.” <coding_guidelines>

Also applies to: 150-152, 299-299

🤖 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 `@src/clients/task-client.test.ts` around lines 110 - 124, Update the relevant
task-client tests, including the “generates a request ID if no run name is
provided” case and the additional indicated assertions, to include the expected
requestId in each returned-result objectContaining check. Use the mock run’s
request identifier so the tests verify requestId propagation alongside id and
status.

Source: Coding guidelines

this.context.logger.prefixed(runRequest.runName).warning('Skipping enqueuing duplicate run name.');
const requestId = this.runSource.getRequestId(runRequest.input, runRequest.options, runRequest.runName);
if (requestIds.has(requestId)) {
this.context.logger.prefixed(requestId).warning('Skipping enqueuing duplicate run name.');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Describe duplicate requests, not duplicate run names.

Unnamed identical requests also enter this branch, so this warning is misleading. Use the Actor client’s “identical Run requests, or requests with the same name” wording.

As per coding guidelines, events should be logged in a format that is “easy to read and debug.” <coding_guidelines>

🤖 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 `@src/clients/task-client.ts` at line 48, Update the warning in the
duplicate-enqueue branch of the task client to describe duplicate requests,
including unnamed identical requests, rather than only duplicate run names.
Reuse the Actor client wording for “identical Run requests, or requests with the
same name,” while preserving the existing requestId-prefixed logger and readable
formatting.

Source: Coding guidelines

Comment on lines +35 to 38
getRequestId(input?: unknown, options?: unknown, runName?: string): string {
if (runName) return runName;
return hashObject({ sourceType: this.type, sourceId: this.id, input, options });
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add direct coverage for request-ID generation.

This new identity path has no corresponding test coverage. Test that runName wins and that generated IDs vary with source ID, input, and options. As per coding guidelines, “Remember to add/fix unit tests”.

🤖 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 `@src/entities/run-source.ts` around lines 35 - 38, Add unit tests for
RunSource.getRequestId covering the runName override and verifying generated IDs
change when source ID, input, or options differ. Use the existing RunSource test
setup and assert stable request-ID behavior without altering the implementation.

Source: Coding guidelines

Comment thread src/types.ts
Comment on lines 508 to +511
/**
* A record of Runs, having their names as keys and their `ActorRun` objects as values.
* A record of Runs, having their names as keys and their `ExtendedActorRun` objects as values.
*/
export type RunRecord = Record<string, ActorRun>;
export type RunRecord = { [requestId: string]: ExtendedActorRun };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update stale run-name documentation.

Run identity is now request-ID based, but these comments still describe name-based lookup/keys.

  • src/types.ts#L508-L511: state that RunRecord is keyed by request ID.
  • src/context/client-context.ts#L52-L62: change the final comment to refer to a request ID, not a name.

As per path instructions, “Where JSDoc exists on exported APIs, verify it is accurate.”

📍 Affects 2 files
  • src/types.ts#L508-L511 (this comment)
  • src/context/client-context.ts#L52-L62
🤖 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 `@src/types.ts` around lines 508 - 511, Update the JSDoc for exported RunRecord
in src/types.ts lines 508-511 to state that records are keyed by request ID
rather than run name. Also update the final comment in
src/context/client-context.ts lines 52-62 to refer to a request ID; no code
behavior changes are needed.

Source: Path instructions

Comment thread src/utils/hash.ts
Comment on lines +3 to +7
function stringifyObject(obj: unknown): string {
return JSON.stringify(
obj,
Object.keys(obj as object).sort((a, b) => a.localeCompare(b)),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'FILES:\n'
git ls-files 'src/utils/hash.ts' 'src/utils/hash.test.ts'

printf '\nOUTLINE hash.ts:\n'
ast-grep outline src/utils/hash.ts --view expanded || true

printf '\nOUTLINE hash.test.ts:\n'
ast-grep outline src/utils/hash.test.ts --view expanded || true

printf '\nhash.ts contents:\n'
cat -n src/utils/hash.ts

printf '\nhash.test.ts contents:\n'
cat -n src/utils/hash.test.ts

printf '\nNode probe:\n'
node - <<'JS'
const obj = { a: { b: 1, c: 2 }, z: 3 };
const keys = Object.keys(obj).sort();
console.log('replacer keys:', keys);
console.log('stringified:', JSON.stringify(obj, keys));
console.log('full stringify:', JSON.stringify(obj));
JS

Repository: apify-professional-services/apify-orchestrator

Length of output: 2249


Canonicalize nested objects without dropping fields. JSON.stringify(..., Object.keys(obj).sort()) acts as a global whitelist, so nested objects lose keys that aren’t present at the top level; e.g. { a: { b: 1 } } and { a: { b: 2 } } can hash to the same ID. Add a case that differs only in a nested value and ensure the hashes diverge.

📍 Affects 2 files
  • src/utils/hash.ts#L3-L7 (this comment)
  • src/utils/hash.test.ts#L17-L20
🤖 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 `@src/utils/hash.ts` around lines 3 - 7, Update stringifyObject to recursively
canonicalize object keys without using the top-level key list as
JSON.stringify’s global whitelist, preserving every nested field. In
src/utils/hash.ts lines 3-7, change stringifyObject accordingly; in
src/utils/hash.test.ts lines 17-20, add a case where objects differing only in a
nested value produce different hashes.

Comment thread src/utils/hash.ts
Comment on lines +10 to +12
export function hashObject(obj: unknown): string {
const str = stringifyObject(obj);
return murmur3(str).toString(16);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -euo pipefail

printf '\n== hash utility ==\n'
git ls-files 'src/utils/*' 'src/**/hash*' 'src/**/stringify*' | sed 's#^`#-` #'
printf '\n-- src/utils/hash.ts --\n'
cat -n src/utils/hash.ts

printf '\n== hash usages ==\n'
rg -n "hashObject\(|murmur3\(|stringifyObject\(" src test

printf '\n== related serializer ==\n'
for f in $(git ls-files 'src/**/stringify*' 'src/**/serialize*'); do
  echo "\n-- $f --"
  cat -n "$f"
done

Repository: apify-professional-services/apify-orchestrator

Length of output: 3097


🏁 Script executed:

set -euo pipefail

printf '\n== package metadata ==\n'
cat -n package.json | sed -n '1,220p'

printf '\n== dependency lock mentions ==\n'
rg -n '"murmurhash-js"|murmurhash-js' package-lock.json pnpm-lock.yaml yarn.lock npm-shrinkwrap.json 2>/dev/null || true

Repository: apify-professional-services/apify-orchestrator

Length of output: 3670


🏁 Script executed:

set -euo pipefail

printf '\n== src/entities/run-source.ts ==\n'
cat -n src/entities/run-source.ts

printf '\n== src/utils/hash.test.ts ==\n'
cat -n src/utils/hash.test.ts

printf '\n== quick serializer probe ==\n'
node --input-type=module <<'JS'
import { murmur3 } from 'murmurhash-js';

function stringifyObject(obj) {
  return JSON.stringify(obj, Object.keys(obj).sort((a,b) => a.localeCompare(b)));
}

const samples = [
  [{a:{x:1,y:2},b:3}, {a:{x:1,y:9},b:3}],
  [{a:1,b:2}, {a:2,b:1}],
  [{a:{nested:{x:1}}, b:2}, {a:{nested:{x:9}}, b:2}],
];

for (const [x,y] of samples) {
  const sx = stringifyObject(x);
  const sy = stringifyObject(y);
  console.log('X', sx);
  console.log('Y', sy);
  console.log('equal?', sx === sy);
  console.log('hashX', murmur3(sx).toString(16));
  console.log('hashY', murmur3(sy).toString(16));
  console.log('---');
}
JS

Repository: apify-professional-services/apify-orchestrator

Length of output: 5027


🏁 Script executed:

set -euo pipefail

printf '\n== find any request-key logic ==\n'
rg -n "requestKey|hashObject\(" src test

Repository: apify-professional-services/apify-orchestrator

Length of output: 4410


Use a deep stable serializer before hashing. src/utils/hash.ts drops nested properties because the replacer array only contains the top-level keys, so different inputs can collapse to the same request ID and get deduplicated as the same run. A recursive stable stringify is needed here.

🤖 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 `@src/utils/hash.ts` around lines 10 - 12, Update hashObject to serialize
objects with a recursive, stable stringify that preserves nested properties and
deterministic key ordering before passing the result to murmur3. Replace or fix
stringifyObject so nested structures cannot collapse into the same hash, while
preserving the existing hexadecimal hash output.

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.

1 participant