Visitar URL original
fix: track and reuse child runs started without a `run_name` by vdusek · Pull Request #1249 · apify/apify-sdk-python · GitHub
Skip to content

fix: track and reuse child runs started without a run_name - #1249

Draft
vdusek wants to merge 7 commits into
masterfrom
fix/list-unnamed-child-runs
Draft

vdusek wants to merge 7 commits into
masterfrom
fix/list-unnamed-child-runs

Conversation

@vdusek

@vdusek vdusek commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

  • Child runs started without a run_name are now recorded in the default KVS too, next to the named ones under __ACTOR_CHILD_RUNS.
  • They show up in Actor.child_runs, and they are reused like named runs.
  • Feature abort_with_parent still requires run_name here, fix: improve abort_with_parent handling of child runs #1250 lifts that.

Implicit keys for child runs without a name

  • A run without a name is recorded under an implicit key: the first 16 hex characters of its checksum.
  • checksum is a SHA-256 hash of the Actor or task ID, the input and the run options.
  • The hashed run options are content_type, build, max_items, max_total_charge_usd, restart_on_error, memory_mbytes, timeout, force_permission_level and webhooks.
  • Options set to None are left out, so a new option added later does not change existing hashes.
  • token, wait, logger and abort_with_parent are not hashed.
  • An identical call without a name finds the same key and follows the same rules as a named run (see below).

What a call does

  • This applies to Actor.start, Actor.call, Actor.start_task and Actor.call_task alike.
  • A call looks up the record under its run_name, or under the implicit key without one, and fetches the recorded run from the API. Then, by the state of the recorded run:
Recorded run What happens
Nothing recorded under the key A new run starts and is recorded.
READY or RUNNING The call reattaches to it.
SUCCEEDED The run is returned as is. Nothing starts.
ABORTING or TIMING-OUT The SDK waits until it is ABORTED or TIMED-OUT, then resurrects it.
ABORTED or TIMED-OUT The run is resurrected with the call's build, memory_mbytes, timeout, max_items, max_total_charge_usd and restart_on_error.
FAILED A new run starts and replaces the record, for any Actor, task, input or run options.
Missing (the API still returns 404 after about 3 seconds of retries) A new run starts and replaces the record.
  • Actor.start and Actor.start_task then return the run right away. Actor.call and Actor.call_task wait for it to finish, except for a SUCCEEDED run, which they return at once.
  • Before a recorded run is reused (the rows from READY to TIMED-OUT), its checksum must match the call. If it does not, the call raises a ValueError and nothing starts. Only a named call can hit this, since the implicit key comes from the checksum.
  • Calls under the same key run one at a time, so two identical calls made at the same moment share one run (see open questions).

Changes in behavior

Compared to 4.2.x:

  • A call without a run_name is recorded in the default KVS and shows up in Actor.child_runs under its implicit key. Before, it was not recorded at all.
  • A repeated identical call without a run_name returns the first run, both within one attempt and after a migration or resurrection. Before, each call started a new run. To start a new run for an identical call, for example when polling, pass a run_name that differs between the calls, such as f'check-{hour}'.
  • Actor.call and Actor.call_task without a run_name start the run and wait for it in the SDK, the same way a named call does. Before, they used the API's call endpoint.
  • A named call also checks the run options. Reusing a run_name with different options, for example another memory_mbytes, raises a ValueError, the same as reusing it with a different Actor or input. The error message now reads "...was already used for a different Actor, task, input or run options."
  • The record has a new runName field and no longer stores startedAt and history. A FAILED or missing run is still replaced, but the replaced run is not kept in the record. Records written by 4.2.x still load.

Record

{
  "scrape-eu": {
    "runId": "HG7ML7M8z78YcAPEB",
    "status": "RUNNING",
    "runName": "scrape-eu",
    "checksum": "60c83d9047178e725469bb7cc6873813272404085a73db08d61b3383f45be044",
    "abortWithParent": false
  },
  "55ca3ada8c31b113": {
    "runId": "Wk3Qf9nRt2LpXy8Ab",
    "status": "SUCCEEDED",
    "runName": null,
    "checksum": "55ca3ada8c31b113f47979aa2893029a5e5c4f8f41b4935b7fd0c4cef1d975ac",
    "abortWithParent": false
  }
}
  • runId: the current run under the key.
  • status: the last status the SDK observed.
  • runName: the name, or null for a run without one.
  • checksum: the hash above, checked before a recorded run is reused.
  • abortWithParent: whether the run is aborted with the parent. It is stored so that runs recorded before a migration can still be aborted.

Open questions

  • This holds within one attempt and after a migration or resurrection, so a call made again after a migration gets its earlier run back.

    async with Actor:
        # Within one attempt: the second identical call returns the first run, no new run starts.
        run_1 = await Actor.call('apify/web-scraper', {'url': 'https://example.com'})
        run_2 = await Actor.call('apify/web-scraper', {'url': 'https://example.com'})
        assert run_2.id == run_1.id
    
        # After a migration or resurrection, this code runs again from the start. The same call
        # computes the same key, finds the run recorded before the migration and returns it
        # (or reattaches to it, if it is still running).
  • Should there be a global switch (a Configuration field with an APIFY_* env var) that turns off reusing child runs without a name?

  • Every unique call without a name adds a record, and the whole __ACTOR_CHILD_RUNS value is rewritten on each change. An Actor that fans out to thousands of children with different inputs writes a growing value thousands of times. Accept it, cap the number of records, or store one KVS key per run?

  • token is not part of the hash, so an identical call with another token reuses the run started with the first one. If that token belongs to another account, the lookup gets a 404, the run counts as missing, and a new run replaces the record. Is that the behavior we want, or should the token (or its account) be part of the key?

✍️ Drafted by Claude Code

@vdusek vdusek added adhoc Ad-hoc unplanned task added during the sprint. t-tooling Issues with this label are in the ownership of the tooling team. labels Nov 12, 2026
@vdusek vdusek self-assigned this Nov 12, 2026
@codecov

codecov Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.36066% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 93.95%. Comparing base (3638c75) to head (5ca3e05).

Files with missing lines Patch % Lines
src/apify/_child_runs.py 98.11% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1249      +/-   ##
==========================================
+ Coverage   93.93%   93.95%   +0.01%     
==========================================
  Files          54       54              
  Lines        3828     3836       +8     
==========================================
+ Hits         3596     3604       +8     
  Misses        232      232              
Flag Coverage Δ
e2e 31.25% <31.14%> (+0.01%) ⬆️
integration 51.04% <32.78%> (+0.02%) ⬆️
unit 91.13% <98.36%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@vdusek vdusek changed the title fix: list unnamed child runs in Actor.child_runs feat: track child runs started without a run_name Nov 12, 2026
@vdusek vdusek changed the title feat: track child runs started without a run_name feat: track and reuse child runs started without a run_name Nov 12, 2026
@vdusek
vdusek added this pull request to stack #1252 November 12, 2026 11:46
@vdusek vdusek changed the title feat: track and reuse child runs started without a run_name fix: track and reuse child runs started without a run_name Nov 12, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

adhoc Ad-hoc unplanned task added during the sprint. t-tooling Issues with this label are in the ownership of the tooling team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant