Repository: celery/celery

Issue:
link_error fails if errback task is written in external codebase, raises NotRegistered error
## Checklist

- [x] I have included the output of ``celery -A proj report`` in the issue.
      (if you are not able to do this, then at least specify the Celery
       version affected).
- [x] I have verified that the issue exists against the `master` branch of Celery.

## Steps to reproduce

### Setup

I have three systems:

- TaskProducer, simply schedules tasks. This happens to be a Django app, however I don't believe that is related to this issue. This system manages the routing for the tasks and queues.
- ExportWorker, handles `export` queue. This system is unaware of the routing for any tasks or queues.
- MessageWorker, handles `msg` queue. This system is unaware of the routing for any tasks or queues.

I'm attempting to keep these systems decoupled.  They share no code.

ExportWorker has a single task:
```python
@app.task(name='export.hello', bind=True)
def hello(self, name='world'):
    if name == 'homer':
        raise Exception("NO HOMERS ALLOWED!")
    return 'hello {}'.format(name)
```

MessageWorker has two tasks:
```python
@app.task(name='msg.success', bind=True)
def email_success(self, msg, email_address):
    return 'Sending email: {}'.format(msg)


@app.task(name='msg.err', bind=True)
def email_err(self, context, exception, traceback):
    print("Handled error: {}".format(exception))
    return 'Something went wrong!'
```

#### Settings

<details>
<summary><code>taskProducer$ celery -A tasks report</code> (brief, only Celery pertinent settings)</summary>

```
software -> celery:4.0.2 (latentcall) kombu:4.0.2 py:3.5.2
            billiard:3.5.0.2 sqs:N/A
platform -> system:Linux arch:64bit, ELF imp:CPython
loader   -> celery.loaders.app.AppLoader
settings -> transport:sqs results:disabled

CELERY_BROKER_TRANSPORT = 'sqs'
CELERY_BROKER_TRANSPORT_OPTIONS = {
    'region': 'us-west-2',
    'queue_name_prefix': 'platform-staging-',
}

CELERY_TASK_ROUTES = {
    'export.*': {'queue': 'export'},
    'import.*': {'queue': 'import'},
    'msg.*': {'queue': 'msg'},
}
CELERY_RESULT_QUEUE = 'result.fifo'
```
</details>

<details>
<summary><code>taskProducer$ celery -A tasks report</code> (long)</summary>

```
software -> celery:4.0.2 (latentcall) kombu:4.0.2 py:3.5.2
            billiard:3.5.0.2 sqs:N/A
platform -> system:Linux arch:64bit, ELF imp:CPython
loader   -> celery.loaders.app.AppLoader
settings -> transport:sqs results:disabled

CACHES: {
    'default': {   'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
                   'LOCATION': 'default'},
    'jsonattrs': {   'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
                     'LOCATION': 'jsonattrs'}}
SETTINGS_MODULE: 'config.settings.dev_debug'
PASSWORD_HASHERS: '********'
OSM_ATTRIBUTION: <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e052e8>
USE_TZ: True
MEDIA_ROOT: '/vagrant/cadasta/core/media'
FORMAT_MODULE_PATH: None
SIGNING_BACKEND: 'django.core.signing.TimestampSigner'
ICON_URL: 'https://s3-us-west-2.amazonaws.com/cadasta-resources/icons/{}.png'
CSRF_COOKIE_HTTPONLY: False
DATETIME_INPUT_FORMATS: ['%Y-%m-%d %H:%M:%S',
 '%Y-%m-%d %H:%M:%S.%f',
 '%Y-%m-%d %H:%M',
 '%Y-%m-%d',
 '%m/%d/%Y %H:%M:%S',
 '%m/%d/%Y %H:%M:%S.%f',
 '%m/%d/%Y %H:%M',
 '%m/%d/%Y',
 '%m/%d/%y %H:%M:%S',
 '%m/%d/%y %H:%M:%S.%f',
 '%m/%d/%y %H:%M',
 '%m/%d/%y']
ICON_LOOKUPS: {
    'application/gpx+xml': 'gpx',
    'application/msexcel': 'xls',
    'application/msword': 'doc',
    'application/pdf': 'pdf',
    'application/vnd.ms-excel': 'xls',
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx',
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',
    'application/xml': 'xml',
    'audio/1d-interleaved-parityfec': 'audio',
    'audio/32kadpcm': 'audio',
    'audio/3gpp': 'audio',
    'audio/3gpp2': 'audio',
    'audio/ATRAC-ADVANCED-LOSSESS': 'audio',
    'audio/ATRAC-X': 'audio',
    'audio/ATRAC3': 'audio',
    'audio/BV16': 'audio',
    'audio/BV32': 'audio',
    'audio/CN': 'audio',
    'audio/DAT12': 'audio',
    'audio/DV': 'audio',
    'audio/DV14': 'audio',
    'audio/EVRC': 'audio',
    'audio/EVRC-QCP': 'audio',
    'audio/EVRC0': 'audio',
    'audio/EVRC1': 'audio',
    'audio/EVRCB': 'audio',
    'audio/EVRCB0': 'audio',
    'audio/EVRCB1': 'audio',
    'audio/EVRCNW': 'audio',
    'audio/EVRCNW0': 'audio',
    'audio/EVRCNW1': 'audio',
    'audio/EVRCWB': 'audio',
    'audio/EVRCWB0': 'audio',
    'audio/EVRCWB1': 'audio',
    'audio/EVS': 'audio',
    'audio/G711-0': 'audio',
    'audio/G719': 'audio',
    'audio/G722': 'audio',
    'audio/G7221': 'audio',
    'audio/G723': 'audio',
    'audio/G726-16': 'audio',
    'audio/G726-24': 'audio',
    'audio/G726-32': 'audio',
    'audio/G726-40': 'audio',
    'audio/G728': 'audio',
    'audio/G729': 'audio',
    'audio/G7291': 'audio',
    'audio/G729D': 'audio',
    'audio/G729E': 'audio',
    'audio/GSM': 'audio',
    'audio/GSM-EFR': 'audio',
    'audio/GSM-HR-08': 'audio',
    'audio/L16': 'audio',
    'audio/L20': 'audio',
    'audio/L24': 'audio',
    'audio/L8': 'audio',
    'audio/LPC': 'audio',
    'audio/MP4A-LATM': 'audio',
    'audio/MPA': 'audio',
    'audio/MPA2': 'audio',
    'audio/PCMA': 'audio',
    'audio/PCMA-WB': 'audio',
    'audio/PCMU': 'audio',
    'audio/PCMU-WB': 'audio',
    'audio/QCELP': 'audio',
    'audio/RED': 'audio',
    'audio/SMV': 'audio',
    'audio/SMV-QCP': 'audio',
    'audio/SMV0': 'audio',
    'audio/UEMCLIP': 'audio',
    'audio/VDVI': 'audio',
    'audio/VMR-WB': 'audio',
    'audio/aac': 'audio',
    'audio/aacp': 'audio',
    'audio/ac3': 'audio',
    'audio/amr': 'audio',
    'audio/amr-wb': 'audio',
    'audio/amr-wb+': 'audio',
    'audio/aptx': 'audio',
    'audio/asc': 'audio',
    'audio/basic': 'audio',
    'audio/clearmode': 'audio',
    'audio/dls': 'dls',
    'audio/dsr-es201108': 'audio',
    'audio/dsr-es202050': 'audio',
    'audio/dsr-es202211': 'audio',
    'audio/dsr-es202212': 'audio',
    'audio/eac3': 'audio',
    'audio/encaprtp': 'audio',
    'audio/example': 'audio',
    'audio/fwdred': 'audio',
    'audio/iLBC': 'audio',
    'audio/ip-mr_v2.5': 'audio',
    'audio/m4a': 'audio',
    'audio/midi': 'audio',
    'audio/mobile-xmf': 'audio',
    'audio/mp3': 'mp3',
    'audio/mp4': 'mp4',
    'audio/mpa-robust': 'audio',
    'audio/mpa-robust3': 'audio',
    'audio/mpeg': 'mp3',
    'audio/mpeg1': 'audio',
    'audio/mpeg3': 'mp3',
    'audio/mpeg4-generic': 'mp4',
    'audio/ogg': 'audio',
    'audio/opus': 'audio',
    'audio/parityfec': 'audio',
    'audio/raptorfec': 'audio',
    'audio/rtp-enc-aescm128': 'audio',
    'audio/rtp-midi': 'audio',
    'audio/rtploopback': 'audio',
    'audio/rtx': 'audio',
    'audio/sp-midi': 'audio',
    'audio/speex': 'audio',
    'audio/t140c': 'audio',
    'audio/t38': 'audio',
    'audio/telephone-event': 'audio',
    'audio/tone': 'audio',
    'audio/ulpfec': 'audio',
    'audio/vorbis': 'audio',
    'audio/vorbis-config': 'audio',
    'audio/wav': 'audio',
    'audio/wave': 'audio',
    'audio/x-flac': 'audio',
    'audio/x-midi': 'audio',
    'audio/x-mpeg-3': 'mp3',
    'audio/x-wav': 'audio',
    'image/gif': 'gif',
    'image/jpeg': 'jpg',
    'image/png': 'png',
    'image/tif': 'tiff',
    'image/tiff': 'tiff',
    'text/csv': 'csv',
    'text/plain': 'csv',
    'text/xml': 'xml',
    'video/mp4': 'mp4',
    'video/mpeg': 'mp3',
    'video/x-mpeg': 'mp3'}
AUTH_PASSWORD_VALIDATORS: '********'
CACHE_MIDDLEWARE_SECONDS: 600
ACCOUNT_LOGOUT_REDIRECT_URL: '/account/login/'
STATICFILES_STORAGE: 'django.contrib.staticfiles.storage.StaticFilesStorage'
DEVSERVER_TRUNCATE_SQL: True
SESSION_FILE_PATH: None
DEBUG: True
LANGUAGE_COOKIE_NAME: 'django_language'
PREPEND_WWW: False
DEFAULT_INDEX_TABLESPACE: ''
ES_HOST: 'localhost'
DEBUG_PROPAGATE_EXCEPTIONS: False
LANGUAGES_BIDI: ['he', 'ar', 'fa', 'ur']
FILE_UPLOAD_HANDLERS: ['django.core.files.uploadhandler.TemporaryFileUploadHandler']
CSRF_COOKIE_DOMAIN: None
SESSION_COOKIE_PATH: '/'
CSRF_FAILURE_VIEW: 'django.views.csrf.csrf_failure'
CSRF_COOKIE_AGE: 31449600
ES_SCHEME: 'http'
STATICFILES_DIRS: []
FILE_UPLOAD_TEMP_DIR: None
SESSION_COOKIE_HTTPONLY: True
DIGITALGLOBE_TILESET_URL_FORMAT: 'https://{{s}}.tiles.mapbox.com/v4/digitalglobe.{}/{{z}}/{{x}}/{{y}}.png?access_toke'
SITE_ID: 1
X_FRAME_OPTIONS: 'SAMEORIGIN'
NUMBER_GROUPING: 0
CELERY_BROKER_TRANSPORT: 'sqs'
EMAIL_TIMEOUT: None
ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL: '/account/login/'
SESSION_COOKIE_DOMAIN: None
EMAIL_SUBJECT_PREFIX: '[Django] '
EMAIL_HOST: 'localhost'
ES_MAX_RESULTS: 10000
BASE_TEMPLATE_DIR: '/vagrant/cadasta/templates'
LEAFLET_CONFIG: {
    'PLUGINS': {   'draw': {'js': '/static/leaflet/draw/leaflet.draw.js'},
                   'groupedlayercontrol': {   'css': '/static/css/leaflet.groupedlayercontrol.min.css',
                                              'js': '/static/js/leaflet.groupedlayercontrol.min.js'}},
    'RESET_VIEW': False,
    'TILES': [   (   <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e44748>,
                     'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
                     {   'attribution': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e052e8>,
                         'maxZoom': 19}),
                 (   <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f940>,
                     'https://{s}.tiles.mapbox.com/v4/digitalglobe.n6ngnadl/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiZ,
                     {   'attribution': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e05358>,
                         'maxZoom': 22}),
                 (   <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f978>,
                     'https://{s}.tiles.mapbox.com/v4/digitalglobe.nal0g75k/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiZ,
                     {   'attribution': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e05358>,
                         'maxZoom': 22}),
                 (   <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f9e8>,
                     'https://{s}.tiles.mapbox.com/v4/digitalglobe.n6nhclo2/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiZ,
                     {   'attribution': (   <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e052e8,
                                            <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e05358,
                         'maxZoom': 22}),
                 (   <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1fa58>,
                     'https://{s}.tiles.mapbox.com/v4/digitalglobe.nal0mpda/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiZ,
                     {   'attribution': (   <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e052e8,
                                            <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e05358,
                         'maxZoom': 22})]}
is_overridden: <bound method Settings.is_overridden of <Settings "config.settings.dev_debug">>
MIDDLEWARE_CLASSES:
    ('debug_toolbar.middleware.DebugToolbarMiddleware',
 'corsheaders.middleware.CorsMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.locale.LocaleMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'django.middleware.security.SecurityMiddleware',
 'audit_log.middleware.UserLoggingMiddleware',
 'simple_history.middleware.HistoryRequestMiddleware')
USE_THOUSAND_SEPARATOR: False
EMAIL_USE_TLS: False
LOGGING: {
    'disable_existing_loggers': False,
    'formatters': {   'simple': {   'format': '%(asctime)s %(levelname)s '
                                              '%(message)s'}},
    'handlers': {   'file': {   'class': 'logging.FileHandler',
                                'filename': '/var/log/django/debug.log',
                                'formatter': 'simple',
                                'level': 'DEBUG'}},
    'loggers': {   'django': {   'handlers': ['file'],
                                 'level': 'DEBUG',
                                 'propagate': True},
                   'xform.submissions': {   'handlers': ['file'],
                                            'level': 'DEBUG'}},
    'version': 1}
DATA_UPLOAD_MAX_MEMORY_SIZE: 2621440
AUTH_USER_MODEL: 'accounts.User'
SESSION_SAVE_EVERY_REQUEST: False
IGNORABLE_404_URLS: []
STATICFILES_FINDERS:
    ('django.contrib.staticfiles.finders.FileSystemFinder',
 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
 'sass_processor.finders.CssFinder')
DATABASES: {
    'default': {   'ENGINE': 'django.contrib.gis.db.backends.postgis',
                   'HOST': 'localhost',
                   'NAME': 'cadasta',
                   'PASSWORD': '********',
                   'USER': 'cadasta'}}
CELERY_BROKER_TRANSPORT_OPTIONS: {
 'queue_name_prefix': 'platform-staging-', 'region': 'us-west-2'}
DEVSERVER_MODULES:
    ('devserver.modules.sql.SQLSummaryModule',
 'devserver.modules.profile.ProfileSummaryModule')
DECIMAL_SEPARATOR: '.'
SESSION_ENGINE: 'django.contrib.sessions.backends.db'
ALLOWED_HOSTS: ['*']
FILE_UPLOAD_PERMISSIONS: None
SESSION_EXPIRE_AT_BROWSER_CLOSE: False
FIXTURE_DIRS: []
TIME_FORMAT: 'P'
SASS_PROCESSOR_INCLUDE_DIRS:
    ('/vagrant/cadasta/core/node_modules',)
DEBUG_TOOLBAR_CONFIG: {
 'SHOW_TOOLBAR_CALLBACK': <function always at 0x7f8467e45400>}
REST_FRAMEWORK: {
    'DEFAULT_AUTHENTICATION_CLASSES': (   'rest_framework.authentication.TokenAuthentication',
                                          'rest_framework.authentication.BasicAuthentication',
                                          'rest_framework.authentication.SessionAuthentication'),
    'DEFAULT_PERMISSION_CLASSES': (   'rest_framework.permissions.IsAuthenticated',),
    'DEFAULT_VERSION': 'v1',
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning',
    'EXCEPTION_HANDLER': 'core.views.api.exception_handler'}
CADASTA_INVALID_ENTITY_NAMES: ['add', 'new']
THOUSAND_SEPARATOR: ','
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS: 2
DATE_INPUT_FORMATS: ['%Y-%m-%d',
 '%m/%d/%Y',
 '%m/%d/%y',
 '%b %d %Y',
 '%b %d, %Y',
 '%d %b %Y',
 '%d %b, %Y',
 '%B %d %Y',
 '%B %d, %Y',
 '%d %B %Y',
 '%d %B, %Y']
SECURE_PROXY_SSL_HEADER: None
FILE_CHARSET: 'utf-8'
SECURE_HSTS_INCLUDE_SUBDOMAINS: False
DEFAULT_CHARSET: 'utf-8'
MESSAGE_STORAGE: 'django.contrib.messages.storage.fallback.FallbackStorage'
FIRST_DAY_OF_WEEK: 0
CSRF_COOKIE_PATH: '/'
FILE_UPLOAD_DIRECTORY_PERMISSIONS: None
CELERY_RESULT_QUEUE: 'result.fifo'
FILE_UPLOAD_MAX_MEMORY_SIZE: 2621440
DEFAULT_FROM_EMAIL: 'testing@example.com'
SESSION_SERIALIZER: 'django.contrib.sessions.serializers.JSONSerializer'
LOGGING_CONFIG: 'logging.config.dictConfig'
USE_L10N: True
LANGUAGE_COOKIE_DOMAIN: None
CSRF_HEADER_NAME: 'HTTP_X_CSRFTOKEN'
EMAIL_USE_SSL: False
TEMPLATES: [{'BACKEND': 'django.template.backends.django.DjangoTemplates',
  'DIRS': ['/vagrant/cadasta/templates',
           '/vagrant/cadasta/templates/allauth'],
  'OPTIONS': {'context_processors': ['django.template.context_processors.debug',
                                     'django.template.context_processors.request',
                                     'django.contrib.auth.context_processors.auth',
                                     'django.contrib.messages.context_processors.messages'],
              'loaders': ['django.template.loaders.filesystem.Loader',
                          'django.template.loaders.app_directories.Loader']}}]
ADMINS: []
SECURE_SSL_REDIRECT: False
LANGUAGE_CODE: 'en-us'
SECURE_REDIRECT_EXEMPT: []
EMAIL_SSL_CERTFILE: None
WSGI_APPLICATION: 'config.wsgi.application'
LANGUAGE_COOKIE_PATH: '/'
DEFAULT_TABLESPACE: ''
CORS_ORIGIN_ALLOW_ALL: False
EMAIL_HOST_PASSWORD: '********'
SHORT_DATE_FORMAT: 'm/d/Y'
LOGIN_REDIRECT_URL: '/dashboard/'
DEFAULT_CONTENT_TYPE: 'text/html'
DATE_FORMAT: 'N j, Y'
EMAIL_HOST_USER: ''
CSRF_COOKIE_NAME: 'csrftoken'
EMAIL_BACKEND: 'django.core.mail.backends.console.EmailBackend'
PASSWORD_RESET_TIMEOUT_DAYS: '********'
CSRF_TRUSTED_ORIGINS: []
BASE_DIR: '/vagrant/cadasta/config'
CACHE_MIDDLEWARE_KEY_PREFIX: '********'
FORM_LANGS: {
    'af': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e16908>,
    'ar': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e16898>,
    'az': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e169e8>,
    'be': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e16a20>,
    'bg': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e16a58>,
    'bn': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e16a90>,
    'br': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e16b00>,
    'bs': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e16b70>,
    'ca': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e16be0>,
    'cs': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e16c50>,
    'cy': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e16cc0>,
    'da': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e16d30>,
    'de': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e16da0>,
    'el': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e16e10>,
    'en': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e16e80>,
    'eo': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e16ef0>,
    'es': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e16f60>,
    'et': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e16fd0>,
    'eu': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c080>,
    'fa': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c0f0>,
    'fi': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c160>,
    'fr': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c1d0>,
    'fy': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c240>,
    'ga': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c2b0>,
    'gd': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c320>,
    'gl': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c390>,
    'he': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c400>,
    'hi': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c470>,
    'hr': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c4e0>,
    'hu': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c550>,
    'ia': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c5c0>,
    'id': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c630>,
    'io': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c6a0>,
    'is': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c710>,
    'it': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c780>,
    'ja': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c7f0>,
    'ka': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c860>,
    'kar': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f8d0>,
    'kk': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c8d0>,
    'km': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c940>,
    'kn': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1c9b0>,
    'ko': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1ca20>,
    'lb': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1ca90>,
    'lt': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1cb00>,
    'lv': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1cb70>,
    'mk': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1cbe0>,
    'ml': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1cc50>,
    'mn': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1ccc0>,
    'mr': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1cd30>,
    'my': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1cda0>,
    'nb': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1ce10>,
    'ne': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1ce80>,
    'nl': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1cef0>,
    'nn': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1cf60>,
    'os': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1cfd0>,
    'pa': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f080>,
    'pl': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f0f0>,
    'pt': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f160>,
    'ro': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f1d0>,
    'ru': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f240>,
    'sk': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f2b0>,
    'sl': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f320>,
    'sq': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f390>,
    'sr': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f400>,
    'sv': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f470>,
    'sw': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f4e0>,
    'ta': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f550>,
    'te': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f5c0>,
    'th': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f630>,
    'tr': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f6a0>,
    'tt': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f710>,
    'uk': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f780>,
    'ur': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f7f0>,
    'vi': <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1f860>}
DATETIME_FORMAT: 'N j, Y, P'
TIME_INPUT_FORMATS: ['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
LANGUAGE_COOKIE_AGE: None
ABSOLUTE_URL_OVERRIDES: {
 }
INTERNAL_IPS:
    ('0.0.0.0',)
SERVER_EMAIL: 'root@localhost'
MIME_LOOKUPS: {
 'gpx': 'application/gpx+xml'}
SITE_NAME: 'Cadasta'
DATA_UPLOAD_MAX_NUMBER_FIELDS: 1000
ACCOUNT_ADAPTER: 'accounts.adapter.DefaultAccountAdapter'
SILENCED_SYSTEM_CHECKS: []
DATABASE_ROUTERS: '********'
LOGOUT_URL: '/account/logout/'
CSRF_COOKIE_SECURE: False
SECURE_BROWSER_XSS_FILTER: False
AUTHENTICATION_BACKENDS: ['core.backends.Auth',
 'django.contrib.auth.backends.ModelBackend',
 'accounts.backends.AuthenticationBackend']
SECURE_SSL_HOST: None
DEFAULT_FILE_STORAGE: 'buckets.test.storage.FakeS3Storage'
EMAIL_PORT: 1025
USE_X_FORWARDED_PORT: False
INSTALLED_APPS:
    ('debug_toolbar',
 'django_extensions',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django.contrib.sites',
 'django.contrib.gis',
 'corsheaders',
 'core',
 'geography',
 'accounts',
 'organization',
 'spatial',
 'questionnaires',
 'resources',
 'buckets',
 'party',
 'xforms',
 'search',
 'tasks',
 'crispy_forms',
 'parsley',
 'widget_tweaks',
 'django_countries',
 'leaflet',
 'rest_framework',
 'rest_framework_gis',
 'rest_framework.authtoken',
 'rest_framework_docs',
 'djoser',
 'tutelary',
 'allauth',
 'allauth.account',
 'allauth.socialaccount',
 'sass_processor',
 'simple_history',
 'jsonattrs')
LOGIN_URL: '/account/login/'
SECURE_CONTENT_TYPE_NOSNIFF: False
SHORT_DATETIME_FORMAT: 'm/d/Y P'
USE_I18N: True
SECURE_HSTS_SECONDS: 0
ACCOUNT_LOGIN_ATTEMPTS_TIMEOUT: 86400
YEAR_MONTH_FORMAT: 'F Y'
APPEND_SLASH: True
MIGRATION_MODULES: {
 }
ES_PORT: '8000'
TEST_RUNNER: 'django.test.runner.DiscoverRunner'
LOCALE_PATHS: ['/vagrant/cadasta/config/locale']
MANAGERS: []
TIME_ZONE: 'UTC'
DEBUG_TOOLBAR_PANELS:
    ('debug_toolbar.panels.version.VersionDebugPanel',
 'debug_toolbar.panels.timer.TimerDebugPanel',
 'debug_toolbar.panels.headers.HeaderDebugPanel',
 'debug_toolbar.panels.request_vars.RequestVarsDebugPanel',
 'debug_toolbar.panels.template.TemplateDebugPanel',
 'debug_toolbar.panels.sql.SQLDebugPanel',
 'debug_toolbar.panels.signals.SignalDebugPanel')
ACCOUNT_FORMS: {
    'profile': 'accounts.forms.ProfileForm',
    'signup': 'accounts.forms.RegisterForm'}
SESSION_COOKIE_NAME: 'sessionid'
MONTH_DAY_FORMAT: 'F j'
SESSION_COOKIE_AGE: 1209600
DIGITALGLOBE_ATTRIBUTION: <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e05358>
DJOSER: {
    'ACTIVATION_URL': 'account/activate/{uid}/{token}',
    'DOMAIN': 'localhost:8000',
    'PASSWORD_RESET_CONFIRM_RETYPE': '********',
    'PASSWORD_RESET_CONFIRM_URL': '********',
    'SERIALIZERS': {'set_password_retype': '********'},
    'SET_PASSWORD_RETYPE': '********',
    'SITE_NAME': 'Cadasta'}
DISALLOWED_USER_AGENTS: []
LANGUAGES: [('en',
  <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1fac8>),
 ('fr',
  <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1fb38>),
 ('es',
  <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1fba8>),
 ('id',
  <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1fc18>),
 ('pt',
  <django.utils.functional.lazy.<locals>.__proxy__ object at 0x7f8467e1fc88>)]
STATIC_URL: '/static/'
ATTRIBUTE_GROUPS: {
    'location_attributes': {   'app_label': 'spatial',
                               'label': 'Location',
                               'model': 'spatialunit'},
    'location_relationship_attributes': {   'app_label': 'spatial',
                                            'label': 'Spatial relationship',
                                            'model': 'spatialrelationship'},
    'party_attributes': {   'app_label': 'party',
                            'label': 'Party',
                            'model': 'party'},
    'party_relationship_attributes': {   'app_label': 'party',
                                         'label': 'Party relationship',
                                         'model': 'partyrelationship'},
    'tenure_relationship_attributes': {   'app_label': 'party',
                                          'label': 'Tenure Relationship',
                                          'model': 'tenurerelationship'}}
SESSION_COOKIE_SECURE: False
ROOT_URLCONF: 'config.urls.dev'
TEST_NON_SERIALIZED_APPS: []
JSONATTRS_SCHEMA_SELECTORS: {
    'party.party': (   'project.organization.pk',
                       'project.pk',
                       'project.current_questionnaire',
                       'type'),
    'party.partyrelationship': (   'project.organization.pk',
                                   'project.pk',
                                   'project.current_questionnaire'),
    'party.tenurerelationship': (   'project.organization.pk',
                                    'project.pk',
                                    'project.current_questionnaire'),
    'spatial.spatialrelationship': (   'project.organization.pk',
                                       'project.pk',
                                       'project.current_questionnaire'),
    'spatial.spatialunit': (   'project.organization.pk',
                               'project.pk',
                               'project.current_questionnaire')}
SASS_PROCESSOR_ROOT: '/vagrant/cadasta/core/static'
SESSION_CACHE_ALIAS: 'default'
SECRET_KEY: '********'
FORCE_SCRIPT_NAME: None
CACHE_MIDDLEWARE_ALIAS: 'default'
ACCOUNT_LOGOUT_ON_GET: True
MIDDLEWARE: None
ACCOUNT_CONFIRM_EMAIL_ON_GET: True
USE_ETAGS: False
CELERY_TASK_ROUTES: {
    'export.*': {'queue': 'export'},
    'import.*': {'queue': 'import'},
    'msg.*': {'queue': 'msg'}}
MEDIA_URL: '/media/'
IMPORTERS: {
    'csv': 'organization.importers.csv.CSVImporter',
    'xls': 'organization.importers.xls.XLSImporter'}
DEVSERVER_AUTO_PROFILE: False
DEFAULT_EXCEPTION_REPORTER_FILTER: 'django.views.debug.SafeExceptionReporterFilter'
STATIC_ROOT: None
LOGOUT_REDIRECT_URL: None
USE_X_FORWARDED_HOST: False
ACCOUNT_AUTHENTICATION_METHOD: 'username_email'
EMAIL_SSL_KEYFILE: '********'
```
</details>


<details>
<summary><code>exportWorker$ celery -A app report</code></summary>

```
software -> celery:4.0.2 (latentcall) kombu:4.0.2 py:3.5.2
            billiard:3.5.0.2 sqs:N/A
platform -> system:Linux arch:64bit, ELF imp:CPython
loader   -> celery.loaders.app.AppLoader
settings -> transport:sqs results:rpc:///

broker_transport: 'sqs'
worker_prefetch_multiplier: 0
broker_transport_options: {
 'queue_name_prefix': 'platform-staging-', 'region': 'us-west-2'}
task_track_started: True
result_backend: 'rpc:///'
os: <module 'os' from '/vagrant/test_worker/env/lib/python3.5/os.py'>
imports:
    ('app.tasks',)
```
</details>


<details>
<summary><code>messageWorker$ celery -A app report</code></summary>

```
software -> celery:4.0.2 (latentcall) kombu:4.0.2 py:3.5.2
            billiard:3.5.0.2 sqs:N/A
platform -> system:Linux arch:64bit, ELF imp:CPython
loader   -> celery.loaders.app.AppLoader
settings -> transport:sqs results:rpc:///

imports:
    ('app.tasks',)
broker_transport: 'sqs'
task_track_started: True
result_backend: 'rpc:///'
os: <module 'os' from '/vagrant/test_worker/env/lib/python3.5/os.py'>
worker_prefetch_multiplier: 0
broker_transport_options: {
 'queue_name_prefix': 'platform-staging-', 'region': 'us-west-2'}
```
</details>

### Causing the error

I spin up ExportWorker with `celery -A app worker -Q export -l INFO` and MessageWorker with `celery -A app worker -Q msg -l INFO` and then schedule the following task from the TaskProduer:

```python
# TaskProducer:
from celery import Signature
Signature(
    'export.hello', args=['homer'], 
    link_error=Signature('msg.err', queue='msg')
).apply_async()
```

## Expected behavior

The `export.hello` task fails on ExportWorker, scheduling the `msg.err` task that is then executed on MessageWorker.

## Actual behavior

The ExportWorker is unable to schedule to followup errback task defined in `link_error`, raising a `NotRegistered` exception:

```python
# TaskProducer:
from celery import Signature
Signature(
    'export.hello', args=['homer'], 
    link_error=Signature('msg.err', queue='msg')
).apply_async()

# ExportWorker:
[2017-05-09 00:14:53,458: INFO/MainProcess] Received task: export.hello[ad4ef3ea-06e8-4980-8d9c-91ae68c2305a]
[2017-05-09 00:14:53,506: INFO/PoolWorker-1] Resetting dropped connection: us-west-2.queue.amazonaws.com
[2017-05-09 00:14:53,517: INFO/PoolWorker-1] Starting new HTTPS connection (9): us-west-2.queue.amazonaws.com
[2017-05-09 00:14:53,918: WARNING/PoolWorker-1] /vagrant/test_worker/env/lib/python3.5/site-packages/celery/app/trace.py:542: RuntimeWarning: Exception raised outside body: Task of kind 'msg.err' never registered, please make sure it's imported.:
Traceback (most recent call last):
  File "/vagrant/test_worker/env/lib/python3.5/site-packages/celery/app/trace.py", line 367, in trace_task
    R = retval = fun(*args, **kwargs)
  File "/vagrant/test_worker/env/lib/python3.5/site-packages/celery/app/trace.py", line 622, in __protected_call__
    return self.run(*args, **kwargs)
  File "/vagrant/test_worker/app/tasks.py", line 9, in hello
    raise Exception("NO HOMERS ALLOWED!")
Exception: NO HOMERS ALLOWED!

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/vagrant/test_worker/env/src/kombu/kombu/utils/objects.py", line 42, in __get__
    return obj.__dict__[self.__name__]
KeyError: 'type'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/vagrant/test_worker/env/lib/python3.5/site-packages/celery/app/trace.py", line 381, in trace_task
    I, R, state, retval = on_error(task_request, exc, uuid)
  File "/vagrant/test_worker/env/lib/python3.5/site-packages/celery/app/trace.py", line 323, in on_error
    task, request, eager=eager, call_errbacks=call_errbacks,
  File "/vagrant/test_worker/env/lib/python3.5/site-packages/celery/app/trace.py", line 157, in handle_error_state
    call_errbacks=call_errbacks)
  File "/vagrant/test_worker/env/lib/python3.5/site-packages/celery/app/trace.py", line 202, in handle_failure
    call_errbacks=call_errbacks,
  File "/vagrant/test_worker/env/lib/python3.5/site-packages/celery/backends/base.py", line 168, in mark_as_failure
    self._call_task_errbacks(request, exc, traceback)
  File "/vagrant/test_worker/env/lib/python3.5/site-packages/celery/backends/base.py", line 174, in _call_task_errbacks
    if arity_greater(errback.type.__header__, 1):
  File "/vagrant/test_worker/env/src/kombu/kombu/utils/objects.py", line 44, in __get__
    value = obj.__dict__[self.__name__] = self.__get(obj)
  File "/vagrant/test_worker/env/lib/python3.5/site-packages/celery/canvas.py", line 490, in type
    return self._type or self.app.tasks[self['task']]
  File "/vagrant/test_worker/env/lib/python3.5/site-packages/celery/app/registry.py", line 19, in __missing__
    raise self.NotRegistered(key)
celery.exceptions.NotRegistered: 'msg.err'

  exc, exc_info.traceback)))
[2017-05-09 00:14:53,996: INFO/MainProcess] Resetting dropped connection: us-west-2.queue.amazonaws.com
[2017-05-09 00:14:53,999: INFO/MainProcess] Starting new HTTPS connection (3): us-west-2.queue.amazonaws.com
[2017-05-09 00:14:54,237: ERROR/MainProcess] Pool callback raised exception: Task of kind 'msg.err' never registered, please make sure it's imported.
Traceback (most recent call last):
  File "/vagrant/test_worker/env/src/kombu/kombu/utils/objects.py", line 42, in __get__
    return obj.__dict__[self.__name__]
KeyError: 'type'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/vagrant/test_worker/env/lib/python3.5/site-packages/billiard/pool.py", line 1748, in safe_apply_callback
    fun(*args, **kwargs)
  File "/vagrant/test_worker/env/lib/python3.5/site-packages/celery/worker/request.py", line 366, in on_failure
    self.id, exc, request=self, store_result=self.store_errors,
  File "/vagrant/test_worker/env/lib/python3.5/site-packages/celery/backends/base.py", line 168, in mark_as_failure
    self._call_task_errbacks(request, exc, traceback)
  File "/vagrant/test_worker/env/lib/python3.5/site-packages/celery/backends/base.py", line 174, in _call_task_errbacks
    if arity_greater(errback.type.__header__, 1):
  File "/vagrant/test_worker/env/src/kombu/kombu/utils/objects.py", line 44, in __get__
    value = obj.__dict__[self.__name__] = self.__get(obj)
  File "/vagrant/test_worker/env/lib/python3.5/site-packages/celery/canvas.py", line 490, in type
    return self._type or self.app.tasks[self['task']]
  File "/vagrant/test_worker/env/lib/python3.5/site-packages/celery/app/registry.py", line 19, in __missing__
    raise self.NotRegistered(key)
celery.exceptions.NotRegistered: 'msg.err'

# (MessageWorker has no output)
```


Being that neither of the workers have any awareness of task routing, I manually set the `queue` for any task that is scheduled on the worker. This technique works for standard `link` operations:

```python
# TaskProducer:
from celery import Signature
Signature(
    'export.hello', args=['world'], 
    link=Signature(
        'msg.success', kwargs={'email_address': 'world@company.com'}, queue='msg'
    )
).apply_async()

# ExportWorker:
[2017-05-09 00:08:29,290: INFO/MainProcess] Received task: export.hello[a08db60b-7c59-478e-9293-c0a716629b11]
[2017-05-09 00:08:30,747: INFO/PoolWorker-1] Resetting dropped connection: us-west-2.queue.amazonaws.com
[2017-05-09 00:08:30,751: INFO/PoolWorker-1] Starting new HTTPS connection (7): us-west-2.queue.amazonaws.com
[2017-05-09 00:08:32,469: INFO/PoolWorker-1] Task export.hello[a08db60b-7c59-478e-9293-c0a716629b11] succeeded in 1.723272997973254s: 'hello world'

# MessageWorker:
[2017-05-09 00:08:32,448: INFO/MainProcess] Received task: msg.success[b728d3c7-34cf-49ea-9cfe-5d374c3c1d0e]
[2017-05-09 00:08:32,497: INFO/PoolWorker-1] Resetting dropped connection: us-west-2.queue.amazonaws.com
[2017-05-09 00:08:32,504: INFO/PoolWorker-1] Starting new HTTPS connection (5): us-west-2.queue.amazonaws.com
[2017-05-09 00:08:32,738: INFO/PoolWorker-1] Task msg.success[b728d3c7-34cf-49ea-9cfe-5d374c3c1d0e] succeeded in 0.24422147497534752s: 'Sending email: hello world'
```

The above code successfully runs `export.hello` on ExportWorker and then passes the results to the `msg.success` task which is run on MessageWorker. For the record, `chain()` works as well. 

Finally, if the errback is included with the callback as a list in the `link` argument, it appears that the errback isn't event attempted:

```python
# On TaskProducer
Signature(
    'export.hello', args=['homer'], 
    link=[
        Signature(
            'msg.success', kwargs={'email_address': 'world@company.com'}, queue='msg'),
        Signature(
            'msg.err', queue='msg')
    ]
).apply_async()


# On ExportWorker
[2017-05-09 00:02:26,406: ERROR/PoolWorker-1] Task export.hello[571fd654-909c-4aa4-b6df-50967d172d9f] raised unexpected: Exception('NO HOMERS ALLOWED!',)
Traceback (most recent call last):
  File "/vagrant/test_worker/env/lib/python3.5/site-packages/celery/app/trace.py", line 367, in trace_task
    R = retval = fun(*args, **kwargs)
  File "/vagrant/test_worker/env/lib/python3.5/site-packages/celery/app/trace.py", line 622, in __protected_call__
    return self.run(*args, **kwargs)
  File "/vagrant/test_worker/app/tasks.py", line 9, in hello
    raise Exception("NO HOMERS ALLOWED!")
Exception: NO HOMERS ALLOWED!

# (MessageWorker has no output)
```

### Ideas

If I move the `email.err` task to ExportWorker, it actually catches and handles the error:

```python
@app.task(name='export.hello', bind=True)
def hello(self, name='world'):
    if name == 'homer':
        raise Exception("NO HOMERS ALLOWED!")
    return 'hello {}'.format(name)


@app.task(name='msg.err', bind=True)
def email_err(self, context, exception, traceback):
    print("Handled error: {}".format(exception))
    return 'Something went wrong!'
```

```python
# TaskProducer:
from celery import Signature
Signature(
    'export.hello', args=['homer'], 
    link_error=Signature('msg.err', queue='msg')
).apply_async()

# ExportWorker:
[2017-05-09 00:31:49,666: INFO/MainProcess] Received task: export.hello[14ca7a6e-6e7a-4b96-b0aa-42f4e4919f53]
[2017-05-09 00:31:49,738: INFO/PoolWorker-1] Found credentials in environment variables.
[2017-05-09 00:31:50,031: INFO/PoolWorker-1] Starting new HTTPS connection (1): us-west-2.queue.amazonaws.com
[2017-05-09 00:31:50,429: WARNING/PoolWorker-1] Handled error: NO HOMERS ALLOWED!
[2017-05-09 00:31:50,430: ERROR/PoolWorker-1] Task export.hello[14ca7a6e-6e7a-4b96-b0aa-42f4e4919f53] raised unexpected: Exception('NO HOMERS ALLOWED!',)
Traceback (most recent call last):
  File "/vagrant/test_worker/env/lib/python3.5/site-packages/celery/app/trace.py", line 367, in trace_task
    R = retval = fun(*args, **kwargs)
  File "/vagrant/test_worker/env/lib/python3.5/site-packages/celery/app/trace.py", line 622, in __protected_call__
    return self.run(*args, **kwargs)
  File "/vagrant/test_worker/app/tasks.py", line 9, in hello
    raise Exception("NO HOMERS ALLOWED!")
Exception: NO HOMERS ALLOWED!

# (MessageWorker has no output)
```
This is despite the fact that ExportWorker should only be reading off of the `export` queue.  **This leads me to believe that the issue may be that the `link_error` logic does not respect the `queue` argument.**  However, I haven't dug into the code enough to verify this.

This is possibly related to #3350.


Discussion:
Reading through the [code](https://github.com/celery/celery/blob/e812c5780b4006516116f059ab498e1f043bdd50/celery/backends/base.py#L174-L175), I see that the `errback` is designed to be run inline if it has a function signature that accepts more than one arg.  Naturally, the system is unable to examine the function signature of a task if that task is not in its codebase, which is where the problem lies.

I'd be curious to learn _why_ Celery was designed to handle errors synchronously. Doesn't this violate the advice for [pursuing granularity](http://celery.readthedocs.io/en/latest/userguide/tasks.html#granularity) when writing tasks?  Additionally, it causes errors such as above.  Or maybe I'm missing something?  If I'm correct about above, how would the maintainers feel about a PR to schedule the `errback` as a task if the above error occurs?  Unfortunately, it will be impossible to distinguish between old-style signatures that only accept a single task id arg and new-style signatures that accept the request, exception, and traceback. If there's a work-around for this that would allow us to support both (or if we could ditch support for old-style signatures), please let me know.  Maybe a user should be able to include this detail as an `option` in the errback signature?

As an aside, the documentation around this topic seems a bit hard to follow.  It is correctly documented, however it's done so in the [Chains documentation](http://docs.celeryproject.org/en/latest/userguide/canvas.html?highlight=link_error#chains):

> The worker won’t actually call the errback as a task, but will instead call the errback function directly so that the raw request, exception and traceback objects can be passed to it.

However, in the [Linking (callbacks/errbacks) documentation](http://docs.celeryproject.org/en/latest/userguide/calling.html?highlight=link_error#linking-callbacks-errbacks) it's described differently:

> You can also cause a callback to be applied if task raises an exception (errback), but this behaves differently from a regular callback in that it will be passed the id of the parent task, not the result. This is because it may not always be possible to serialize the exception raised, and so this way the error callback requires a result backend to be enabled, and the task must retrieve the result of the task instead.

Is there a difference between the errback mentioned in the Chains documentation and the errback mentioned in the Linking documentation?
A note that `link_error` appears to work correctly if the error occurs within a `chord`:

```python
from celery.canvas import chord, signature

from .celery import app


@app.task
def export_foo():
    # do some things
    pass

@app.task
def export_bar():
    # do some things
    pass

@app.task
def create_zip():
    # do some things
    pass

def export():
    _chain = chord([
        export_foo.s(),
        export_bar.s(),
    ])
    callback = create_zip.s().set(link_error=signature('msg.email_err'))
    _chain(callback)
```

If the exception occurs in `export_foo` or `export_bar`, the `msg.email_err` task is properly scheduled and run on the remote worker.  However if the exception occurs in the `create_zip` task, the `link_error` is attempted to be run inline and the `celery.exceptions.NotRegistered` will be raised.
@alukach I am facing a similar problem with link_error being called synchronously. Were you able to find a solution or patch? 
@alukach Taking inspiration from your case where chord callbacks work; link_error(errback) works when errback is a chain.

app1.py
```
@app.task(name='export.hello', bind=True, queue="export")
def hello(self, name='world'):
    if name == 'homer':
        raise Exception("NO HOMERS ALLOWED!")
    return 'hello {}'.format(name)
```
app2.py
```
@app.task(name='msg.success', bind=True)
def email_success(self, msg, email_address):
    return 'Sending email: {}'.format(msg)


@app.task(name='msg.err', bind=True)
def email_err(self):
    print("Handled error")
    return 'Something went wrong!'
```

main.py
```
from celery import signature
import app2

_sign = signature(
    'export.hello', args=['homer'], 
    link_error=(app2.email_err.si()|app2.email_err.si()),
    app=app2.app,
    queue="export"
)

_sign.apply_async()
```
@karan718 Interesting, so your example then runs `email_err` twice, correct?  Good find though, I think there's clearly room for a patch to be made, I'd be interested to hear the input of some maintainers on this issue before submitting a patch (/ping @thedrow, @georgepsarakis, @auvipy ?)
@alukach you could replace one of the calls to email_err with a dummy task. Its not pretty.
@karan718 yeah, not the prettiest but working is definitely better than broken. Thanks for sharing.
I agree. A patch is worthwhile in this case.
I am facing this issue with celery 4.1.
I have 2 celery workers attached to one broker, but without sharing the code.

    current_app.send_task('gsclient.run_task', kwargs={'task_id': task.id}, queue=queue_name,
                          link=complete_task.subtask(kwargs={'task_id': task_id}),
                          link_error=complete_task.subtask(kwargs={'task_id': task_id}),
                          )

When the task finishes without error - `link` is called and executed. In case of error `link_error` is not called with the stack trace from the first post of this thread.

In my case workaround will be to wrap the task to handle all exceptions, but return error code.
This might have been addressed by commit https://github.com/celery/celery/commit/bc366c887f194a3ff1f99671d9742a525a066339 : "*Added support to bind=True task as link errors*"
We have run into same issue, so I don't think this should be closed yet.

1. Worker API: Celery 4.2.1
2. Worker Search: Celery 4.2.1

API triggers task on `search` queue with `link` and `link_error`:
```python
eventbus.send_task(
            task_name,
            (event_name, data,),
            queue=queue_name,
            link=signature("ahoy.eventbus.tasks.eventbus_success", args=(event_name, data, receiver),
                           queue="eventbus-coreapi"),
            link_error=signature("ahoy.eventbus.tasks.eventbus_failure", args=(event_name, data, receiver),
                                 queue="eventbus-coreapi"),
            **kwargs
        )
```

After Search Worker processes the task, then `eventbus_success` is triggered correctly on `api` queue. However, `eventbus_failure` is executed on `search` worker, even that the queue is specified.

Exception from `search` worker:
```
File "/usr/local/lib/python3.5/dist-packages/billiard/pool.py", line 1747, in safe_apply_callback
    fun(*args, **kwargs)
  File "/usr/local/lib/python3.5/dist-packages/celery/worker/request.py", line 367, in on_failure
    self.id, exc, request=self, store_result=self.store_errors,
  File "/usr/local/lib/python3.5/dist-packages/celery/backends/base.py", line 162, in mark_as_failure
    self._call_task_errbacks(request, exc, traceback)
  File "/usr/local/lib/python3.5/dist-packages/celery/backends/base.py", line 171, in _call_task_errbacks
    not isinstance(errback.type.__header__, partial) and
  File "/usr/local/lib/python3.5/dist-packages/kombu/utils/objects.py", line 44, in __get__
    value = obj.__dict__[self.__name__] = self.__get(obj)
  File "/usr/local/lib/python3.5/dist-packages/celery/canvas.py", line 471, in type
    return self._type or self.app.tasks[self['task']]
  File "/usr/local/lib/python3.5/dist-packages/celery/app/registry.py", line 21, in __missing__
    raise self.NotRegistered(key)
celery.exceptions.NotRegistered: 'ahoy.eventbus.tasks.eventbus_failure'
```
This issue still actual.
Two app, different queues, one worker for one queue.
When call for external task and err_handler local with 
`s = celery.signature('external_app.tasks.task', options={'link_error': err_handler}) `
external_app worker complains that err_handler NotRegistered (maybe err_handler goes to wrong queue?)
But works fine
```
s = celery.signature('external_app.tasks.task')
s = s.link_error(err_handler)
```
So possible workaround to use link_error() when errback in different codebase than calling task.
**UPDATE**:
I haven't noticed that link_error returns its argument. So in second case err_handler called directly. Not solution.
Can you please try master?
Yes, I am on celery==4.3.0rc1

Resolving patch:
diff --git a/celery/backends/base.py b/celery/backends/base.py
--- a/celery/backends/base.py
+++ b/celery/backends/base.py
@@ -25,7 +25,7 @@
 from celery import current_app, group, maybe_signature, states
 from celery._state import get_current_task
 from celery.exceptions import (ChordError, ImproperlyConfigured,
-                               TaskRevokedError, TimeoutError)
+                               NotRegistered, TaskRevokedError, TimeoutError)
 from celery.five import PY3, items
 from celery.result import (GroupResult, ResultBase, allow_join_result,
                            result_from_tuple)
@@ -168,22 +168,33 @@ def _call_task_errbacks(self, request, exc, traceback):
         old_signature = []
         for errback in request.errbacks:
             errback = self.app.signature(errback)
-            if (
-                    # Celery tasks type created with the @task decorator have
-                    # the __header__ property, but Celery task created from
-                    # Task class do not have this property.
-                    # That's why we have to check if this property exists
-                    # before checking is it partial function.
-                    hasattr(errback.type, '__header__') and
-
-                    # workaround to support tasks with bind=True executed as
-                    # link errors. Otherwise retries can't be used
-                    not isinstance(errback.type.__header__, partial) and
-                    arity_greater(errback.type.__header__, 1)
-            ):
-                errback(request, exc, traceback)
-            else:
+            if not errback._app:
+                # Ensure all signatures have an application
+                errback._app = self.app
+            try:
+                if (
+                        # Celery tasks type created with the @task decorator have
+                        # the __header__ property, but Celery task created from
+                        # Task class do not have this property.
+                        # That's why we have to check if this property exists
+                        # before checking is it partial function.
+                        hasattr(errback.type, '__header__') and
+
+                        # workaround to support tasks with bind=True executed as
+                        # link errors. Otherwise retries can't be used
+                        not isinstance(errback.type.__header__, partial) and
+                        arity_greater(errback.type.__header__, 1)
+                ):
+                    errback(request, exc, traceback)
+                else:
+                    old_signature.append(errback)
+            except NotRegistered:
+                # Task may not be present in this worker.
+                # We simply send it forward for another worker to consume.
+                # If the task is not registered there, the worker will raise
+                # NotRegistered.
                 old_signature.append(errback)
+
         if old_signature:
             # Previously errback was called as a task so we still
             # need to do so if the errback only takes a single task_id arg.


Test patch:
diff --git a/t/unit/backends/test_base.py b/t/unit/backends/test_base.py
--- a/t/unit/backends/test_base.py
+++ b/t/unit/backends/test_base.py
@@ -8,7 +8,7 @@
 from case import ANY, Mock, call, patch, skip
 from kombu.serialization import prepare_accept_content
 
-from celery import chord, group, states, uuid
+from celery import chord, group, signature, states, uuid
 from celery.app.task import Context, Task
 from celery.backends.base import (BaseBackend, DisabledBackend,
                                   KeyValueStoreBackend, _nulldict)
@@ -399,6 +399,18 @@ def run(self):
         b.mark_as_failure('id', exc, request=request)
         mock_group.assert_called_once_with(request.errbacks, app=self.app)
 
+    @patch('celery.backends.base.group')
+    def test_unregistered_task_can_be_used_as_error_callback(self, mock_group):
+        b = BaseBackend(app=self.app)
+        b._store_result = Mock()
+
+        request = Mock(name='request')
+        request.errbacks = [signature('doesnotexist',
+                                      immutable=True)]
+        exc = KeyError()
+        b.mark_as_failure('id', exc, request=request)
+        mock_group.assert_called_once_with(request.errbacks, app=self.app)
+
     def test_mark_as_failure__chord(self):
         b = BaseBackend(app=self.app)
         b._store_result = Mock()